<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>코딩하는 겸</title>
    <link>https://mingyum119.tistory.com/</link>
    <description>  Github : https://github.com/Mingyum-Kim</description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 13:34:27 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>MINGYUM</managingEditor>
    <image>
      <title>코딩하는 겸</title>
      <url>https://tistory1.daumcdn.net/tistory/4482312/attach/96fcc08891e14a0c8bfac71680ab2c4a</url>
      <link>https://mingyum119.tistory.com</link>
    </image>
    <item>
      <title>토종 한국인  개발자의 외국 회사에서의 우당탕탕 1년 회고  </title>
      <link>https://mingyum119.tistory.com/381</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;br /&gt;취업을 했던 1년 전부터 지금까지를 돌이켜보며 개발자로서 어떤 고민과 성장이 있었는지를 정리해보았다.&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;첫 취업과 코흘리개 신입 개발자의 고군분투&amp;nbsp;&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;b&gt;재미있다&lt;/b&gt;는 것을 깨달았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 치는 게 재미있었다기보다는, 티켓을 받고, 문제의 원인을 찾고, 어떤 액션을 통해 문제를 해결할 수 있을 지 파악하고, 실제로 해결하여 나의 온전한 노력으로 끝까지 결과를 이끌어내는 과정이 재미있었다. 한국인의 긍지를 보여주마! 이런 느낌 .. 그 과정에서 우리 팀의 백그라운드를 이해하고 도메인 지식을 넓혀가는 감각도 좋았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발이 재미있다는 깨달음은 상당히 큰 소득이다. 개발자라는 나의 첫 직업에 대한 자신이 생기고 앞으로도 이 직업으로 먹고 살기 위해 투자해도 되는 근거가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 회사 일은 당연하게도 재미있는 일만 있지는 않았다. 내가 이걸 해결할 수 있을까 의문이 들 정도로의 어려움을 경험하기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 크게 느낀 어려움은 &lt;b&gt;커뮤니케이션&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀원들과의 디스커션에 적극적으로 참여하지 못하는 상황이 특히 힘들었다. 나에게 이게 유독 힘들었던 건 물론 언어 차이의 문제도 있다. 초반에는 아예 언어적인 장벽 때문에 알아듣지 못하는 대화가 있었고, 무언가 말하고 싶은게 있더라도 한 문장을 제대로 완성하지 못해 자신감이 많이 떨어졌다. 한 마디도 꺼내지 못하고 디스커션이 종료되는 상황이 적지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 팀과의 대화에서 위축되었던 이유는 비단 언어 문제 때문만은 아니었다. 이는 무엇보다 &lt;b&gt;타인의 시선에서 나를 평가하는 습관&lt;/b&gt;에서 비롯되었다고 생각한다. 질문을 하기 전에 많이 고민하고, 혹시 틀린 질문일까 고민한다. 일을 하는 과정에서 무언가 잘 안되는 모습을 보여주기보다는 혼자 잘 해결해서 완성된 결과를 보여주고 싶어한다. 즉, 의견을 내거나 상황을 공유하여 평가받는 것보다는 맡은 일을 성실히 수행하는 편을 선택했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 지나면서 내 습관이 나의 성장을 얼마나 저해하고 있는지 느끼게 되었다. 내가 모든 걸 다 혼자 해결할 수 있는 능력자가 아닌 이상 나의 상황을 더 많이 공유하고 더 많이 질문하고 더 많이 소통하는 것이 나에게 도움이 된다는 사실을 깨달았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 깨달았다고 해서 문화, 교육으로부터 오랜 시간 축적되어 온 습관이 금방 변하지는 않았다. 컴포트 존이 상당히 견고해서 한 발 밖으로 나가볼 때마다 낙담하고 다시 숨기 마련이었다. 그래서 이게 과연 해결되는 문제가 맞을까 의문이 들기도 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;하라는 거 성실히 잘하는 것도 중요하지만 나의 상황을 적극적으로 공유하고 질문하며 소통하는 것이 필요함을 깨달을 수 있었다.&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;/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;문제를 조사하면서 얻은 지식을 정리하고 팀과 공유하여 토론을 이끌어내는 것은 상당한 스킬이 필요하다. 이렇게 생각한 이유는 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질문을 하는 것도 결국에는 상황을 입체적으로 보며 창의적인 사고를 할 때 자연스럽게 떠오르는 것이다.&lt;/li&gt;
&lt;li&gt;그리고 상대방이 이해하기 쉽게 설명하고 매끄럽게 대화가 이어지도록 하는 것도 아직 어렵다.&lt;/li&gt;
&lt;li&gt;도메인 지식이 충분하고 서비스가 어떻게 돌아가는 지 잘 알고 있어야 풍부한 대화가 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국에 커뮤니케이션은 '창의성'과 '도메인 지식'과 '대화 능력'이 어우러진 아름다운 결과물이라고 정의 내렸다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커뮤니케이션을 '하는 것'까지는 성공했으니 '잘 하는 방법'에 대해 배워갈 차례이다. 내가 나름 내린 액션 플랜은 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;창의성을 키우기 위해서는 당연한 것에 의문을 가지고 질문해야한다.&lt;/li&gt;
&lt;li&gt;도메인 지식을 키우기 위해서는 우선 주어진 티켓에 열심히 임한다.&lt;/li&gt;
&lt;li&gt;대화 능력을 키우기 위해서는 내가 하고자 하는 말을 들을 상대방 입장을 생각하고 이해 가능하게 말하도록 노력한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;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;h3 data-ke-size=&quot;size23&quot;&gt;오너십이란? 개발자가 프로덕트에 오너십을 갖는 방법에 대해&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테코 때의 팀 프로젝트 경험을 통해 개발자로서 '사용자 가치'를 신경쓰며 개발하는 게 중요하다고 생각하게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 만드는 프로덕트의 영향을 고려하며 개발한다는 것인데, 이렇게 오너십이 있을 수록 더 재미있게 일할 수 있고, 더 성장할 수 있다는 것을 알게 되었기 때문이다. 당시 팀 프로젝트는 기획 단계부터 개발, 배포, 운영까지 책임지니 오너십을 가지기 쉬운 환경이라 사용자 가치를 고려하는게 당연하고 또 중요하다고 생각했다.&lt;/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;기획 / 디자인에 관여할 수 있는 범위가 줄어들고, 프로덕트가 크고 여기저기 연결되어있는 곳이 많고, 매니저와 PM 레벨에서 주로 필요한 내용이 정해져서 내려오니 개발자는 개발만 하면 되는 일이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 일 적응하고 받은 티켓을 처리하는 데만 시간을 쏟다보니 처음 6개월 정도는 오너십이라는 건 생각도 못했다. 그러다 어느 순간 가벼운 매너리즘에 빠진 적이 있다. 뭔가 능동적으로 일한다는 느낌보다는 기계적으로 주어진 솔루션을 개발만 하고 있다는 생각이 들어서이다.&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;처음에는 회사의 구조, 일하는 방식이 문제라고 생각했다. 스타트업이 일하는 방식처럼 기획 단계에도 참여하고, 정해진 문제를 푸는 게 아니라 유저와 가까운 거리에서 직접 문제를 찾을 수 있다면 능동적으로 일할 수 있을 거라고 생각했다. 하지만 매니저나 PM 급이 아닌 이상 기획에 참여하기는 제한적이었고 이러한 환경에서도 주도성을 발휘할 수 있는 방법을 찾지 못해 헤맸다.&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;그 분을 관찰하며 이렇게 자신의 영역을 벗어나 다른 부분에도 호기심을 가지고 찾아보고 개선점을 찾는 등 주도적으로 일할 때 &lt;b&gt;기술적인 도전&lt;/b&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인풋 채우기 : 문제를 찾고 정의하고 조사하고 솔루션을 생각해보고 어떤 게 좋은 솔루션일 지 고민한다. 당연해 보여도 질문하는 사고를 가지고 창의적으로 문제를 바라본다. 엣지 케이스를 미리 찾아본다.&lt;/li&gt;
&lt;li&gt;아웃풋 내기 : 조사한 것을 팀이 이해하기 쉽게 정리하여 공유하면서 디스커션이 필요한 부분을 제기한다. 디스커션에 참여하며 질문을 던지고 의견을 요청하고 나의 생각을 제안한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;잘 마무리하기 : 디스커션의 결론을 실행하고 스테이징에서 테스트한다. 팀에 공유하여 피드백을 받는다. 배포 후 프로덕션에서 테스트한다. 배포가 마무리되었다고 공유한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 이상적인 방식의 일하는 방법을 정리해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실적으로는 이 모든 걸 다 신경쓰며 일하기 어렵겠지만 깨달은 바를 열심히 실천해나가다보면 조금씩은 좋은 방향으로 발전할 수 있을 것이다. 아잣&lt;/p&gt;
&lt;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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;AI 시대에서 살아남는 개발자란!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;취업 직후부터 점점 현업에 AI가 들어오기 시작하면서 개발자들의 일하는 방식을 많이 바꾸어놓았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 코딩하는 시대가 끝났다는 말이 정말 현실이 되었고 하드 스킬보다는 소프트 스킬이 더 중요해짐을 느끼는 요즘이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 내가 더 발전해야한다고 생각했던 부분들도 개발 능력보다는 대부분 소프트 스킬에 대해서이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 다음과 같은 능력들이 더 중요해질 것이라고 생각한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제를 발견하고 제기하는 능력&lt;/li&gt;
&lt;li&gt;다양한 해결책을 비교하고 토론하는 능력&lt;/li&gt;
&lt;li&gt;테스트하고 검증하는 능력&lt;/li&gt;
&lt;li&gt;의문을 가지고 깊이 파고드는 능력&lt;/li&gt;
&lt;li&gt;리스크를 미리 보고 공유하는 능력&lt;/li&gt;
&lt;li&gt;소통을 통해 결과를 만들어내는 능력&lt;/li&gt;
&lt;li&gt;문제를 끝까지 해결하려는 집요함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로덕트와 사용자의 커넥션은 앞으로도 계속, 혹은 더 많아질 것이다. AI 가 생기면서 풀 수 있는 문제가 많아지기 때문에, 사람들은 더 편리함을 원할 것이라 생각한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 프로덕트의 주인은 항상 필요할 것이다. 프로덕트를 개발하고 유지보수하며 세상과의 커넥션을 위해 노력하는 사람은 필요하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 시간과 노력이 프로덕트에 기여하는 데 도움이 되는 상태를 유지하기 위해서는 결국 프로덕트에 주인 의식을 가져야 한다. 주인이 아닌 도구가 된다면 대체되기 쉽기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 해야할 것은 지표나 로그를 하나라도 더 보면서 모니터링하고, 엣지 케이스를 확인하고, 회의나 대화에서 한 문장이라도 더 말하면서 &lt;b&gt;팀 내에서 능동적으로 일하는 태도&lt;/b&gt;를 배우고 갖추는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에는 사이드 프로젝트를 하나 시작해서 주변 사람들이 사용하는 서비스를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 만든 서비스이고 다른 사람들이 실제로 사용하고 있으니 자연스럽게 주인 의식을 가지게 되고 재미있게 개발하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 현업에서도 조금 더 '내 서비스'라는 걸 인지하고 더 주도성을 발휘할 수 있다면 좋겠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 1년도 화이팅!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Other/기록</category>
      <author>MINGYUM</author>
      <guid isPermaLink="true">https://mingyum119.tistory.com/381</guid>
      <comments>https://mingyum119.tistory.com/381#entry381comment</comments>
      <pubDate>Mon, 9 Mar 2026 05:07:42 +0900</pubDate>
    </item>
    <item>
      <title>SQS 메시지 유실이 발생하는 원인을 CloudWatch 지표로 분석하기</title>
      <link>https://mingyum119.tistory.com/380</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;SQS &amp;rarr; Consumer 파이프라인에서 메시지 수신이 실패한 상황에서 그 원인과 대응 전략을 회고하기 위한 글이다.&lt;/span&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;b&gt;무슨 일이 있었나?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;SNS &amp;rarr; SQS &amp;rarr; Consumer 구조는 AWS에서 흔하게 쓰이는 패턴이다. 우리도 이 시스템을 채택하고 있었고 한 서비스가 하나의 큐를 리스닝하고 있는 구조이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그러나 운영 중에 서비스가 동작하지 않는다는 제보가 생겼고 큐를 확인했더니 메인 큐에 여러 메시지가 쌓여있는 것을 보았다.  &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;하지만 DLQ에는 쌓여있는 메시지가 0개였다.&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;실패된 메시지에 대해서는 3번 재시도 후 DLQ로 빠지고 DLQ의 메시지 수가 증가하면 알림이 트리거 되어야 한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;하지만 메시지가 DLQ에도 없고 메인 큐에서도 지속적으로 증가하지 않고 메시지가 증가했다가 사라지는 패턴을 반복하고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 실패된 메시지가 재시도 되어 DLQ로 빠지지 못하고 메인 큐에 계속 쌓여있었을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;큐가 소비되지 못하고 메시지에 쌓인 이유&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;우선 가장 근본적인 원인은 설정 오류로 인해 Consumer가 큐를 찾지 못하고 메시지를 소비하지 못한 것이 원인이었다.&amp;nbsp; 그래서 일정 기간 메시지가 큐에 쌓여있었고, Retention Period (1일) 이후에 자동으로 큐에서 삭제된 것이다.&amp;nbsp;&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;1354&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4W2is/dJMcafL7zBs/iiMlMKwzzrG400IlDNQ560/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4W2is/dJMcafL7zBs/iiMlMKwzzrG400IlDNQ560/img.png&quot; data-alt=&quot;정상적으로 소비하는 플로우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4W2is/dJMcafL7zBs/iiMlMKwzzrG400IlDNQ560/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4W2is%2FdJMcafL7zBs%2FiiMlMKwzzrG400IlDNQ560%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;721&quot; height=&quot;197&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;370&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;SQS 메인 큐에 있는 메시지는 Consumer가 Polling 방식으로 ReceiveMessage API를 전송해 가져간다. Consumer가 메시지를 잘 받았다고 DeleteMessage API를 보내야 SQS는 '아 적어도 하나의 Consumer가 메세지를 잘 받았구나' 하고 안전하게 삭제할 수 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;DLQ로 메시지가 전달되지 않은 이유?&amp;nbsp;&lt;/b&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;1530&quot; data-origin-height=&quot;1430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ou7PF/dJMcabiEHgn/Hj9vmiep3xegy9SFhO8j51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ou7PF/dJMcabiEHgn/Hj9vmiep3xegy9SFhO8j51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ou7PF/dJMcabiEHgn/Hj9vmiep3xegy9SFhO8j51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fou7PF%2FdJMcabiEHgn%2FHj9vmiep3xegy9SFhO8j51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;642&quot; height=&quot;600&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;1430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;DLQ는 메인 큐에서 일정 횟수 이상 리트라이된 메시지가 이동하는 곳이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;메시지가 Consumer로 전달될 때마다 메시지의 &lt;b&gt;RecieveCount가 1 씩 증가&lt;/b&gt;한다. Consumer에서 메시지를 처리하지 못하면 다시 재시도하는데, 이때 &lt;b&gt;MaxRecieveCount에 도달&lt;/b&gt;하게 되면 해당 메시지는 DLQ로 빠진다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;메시지가 리트라이가 되기 위해서는 Consumer가 메시지를 소비해야만 가능하다. 하지만 SQS와 Consumer의 파이프라인이 고장나있었기 때문에 리트라이가 되지 않았고, DLQ로 이동하지 못한 채 메인 큐에 쌓여있었던 것이다.&amp;nbsp;&lt;/span&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;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;대응을 위한 CloudWatch 지표 알아보기&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;1.&amp;nbsp; NumberOfMessagesReceived / MessagesDeleted&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;비정상 구간에서 MessagesReceived가 0에 수렴하고, MessagesDeleted도 0에 가까웠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이건 Consumer가 처리를 시도했는데 실패했다는 뜻이 아니라,&amp;nbsp;아예 가져가질(Receive) 않았고, 따라서 지울(Delete) 수도 없었다는 의미다. 즉 Consumer가 정상적으로 큐를 읽고 있지 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;2. &lt;b&gt;NumberOfEmptyReceives&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;정상적으로 polling하는 Consumer는 메시지가 없을 때도 ReceiveMessage를 날리고, 그 결과가 empty일 수밖에 없다. &lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그래서 평소에는 이 지표가 어느 정도 나온다. &lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;근데 비정상 구간에서는 EmptyReceives가 0이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이건 메시지가 없어서 empty가 아니라, 폴링 자체가 멈췄다는 뜻이었다. SQS-Consumer 파이프라인이 고장났음을 알아챌 수 있었던 중요한 지표임을 알게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;3. ApproximateAgeOfOldestMessage ⭐️&lt;b&gt;⭐️&lt;/b&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;메인 큐 내에서 &lt;b&gt;가장 오래된 메시지&lt;/b&gt;의 나이 (큐에 들어온 이후로 유지된 기간) 에 대한 지표이다. 비정상 구간에서&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;ApproximateAgeOfOldestMessage가 86,400초(즉, 1일) 근처에서 평평하게 유지되는 구간이 있었다.&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;958&quot; data-origin-height=&quot;602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/etEaod/dJMcac9DZxz/pRdEeswK8BEJnerzXkpcgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/etEaod/dJMcac9DZxz/pRdEeswK8BEJnerzXkpcgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/etEaod/dJMcac9DZxz/pRdEeswK8BEJnerzXkpcgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FetEaod%2FdJMcac9DZxz%2FpRdEeswK8BEJnerzXkpcgK%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;414&quot; height=&quot;260&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;602&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;현재 큐 세팅에서 Retention Period가 1일이기 때문에, &lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;가장 오래된 메시지는 24시간이 되는 순간 &lt;b&gt;만료로 삭제&lt;/b&gt;된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그래서 oldest age가 계속 올라가서 며칠이 되는 게 아니라, &lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1일 근처에서 더 이상 못 올라가고 유지되는 모양이 나온다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그렇기 때문에 이 지표는 Retention Period 보다 높아질 수 없고 이미 이 지표가 Retention Period와 같게 표시된다? 그러면 이미 여러 개의 메시지가 삭제된 상태일 것이다.   이렇게 삭제된 메시지는 DLQ에도 남지 않기 때문에, 조기에 발견하기 위해서 Retention Period보다 낮은 Threshold로 알림을 걸어놓고 모니터링하는 것이 중요하다.&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;/span&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;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ApproximateAgeOfOldestMessage라는 지표에 대해 잘 몰랐으며 메인 큐의 Retention Period가 존재함도 모르고 있었다. 하지만 이번 기회에 SQS에 대해서 이해할 수 있었고, 이 지표를 잘 모니터링하는 게 중요하다고 느꼈다.&lt;/li&gt;
&lt;li&gt;DLQ의 동작 원리에 대해 이해할 수 있었다. Spring Boot에서는 &lt;i&gt;SQSTextMessage.acknowledge()&lt;/i&gt;를 사용하여 '메시지를 잘 받았고 재시도 처리를 하지 않아도 된다'라는 신호를 보낸다. &amp;nbsp;만약 어느 Consumer에서도 이 신호를 받지 못하면 그 메세지는 DLQ로 넘어가지도 않고 메인 큐에 저장되어있다가 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Retention Period가 지나면 삭제된다.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m2U2f/dJMcaaxhliY/ZDaIGbYITzJcnifiQdUCKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m2U2f/dJMcaaxhliY/ZDaIGbYITzJcnifiQdUCKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m2U2f/dJMcaaxhliY/ZDaIGbYITzJcnifiQdUCKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm2U2f%2FdJMcaaxhliY%2FZDaIGbYITzJcnifiQdUCKK%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;517&quot; height=&quot;137&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;즉, DLQ로 넘어가기 위해서는 적어도 Consumer가 Receive할 수 있어야 가능한 것이다. Consumer가 Receive 해서 여러 번 재시도 했을 때 실패하면 그제서야 DLQ 로 넘어갈 수 있는 것이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Server/MQ</category>
      <author>MINGYUM</author>
      <guid isPermaLink="true">https://mingyum119.tistory.com/380</guid>
      <comments>https://mingyum119.tistory.com/380#entry380comment</comments>
      <pubDate>Sat, 17 Jan 2026 04:31:43 +0900</pubDate>
    </item>
    <item>
      <title>일로, 여행으로 가득 채운 2025 하반기  &amp;zwj; </title>
      <link>https://mingyum119.tistory.com/379</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2025가 끝나고 하반기를 돌아보았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;엄마와 함께 유럽 여행&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025년 하반기에 가장 뜻깊었던 일이라면 엄마와 단둘이 유럽 여기저기를 다닌 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스무살 되고 나서 본가와 먼 학교에서 자취하며 지내다보니 성인 이후 부모님과 교류가 적었다. 그래서 나 자신에 대해서 알아갈 시간은 많았지만 부모님에 대해서 알아갈 시간은 적었던 것 같다. 그러던 중 해외살이를 시작하게 되었고, 부모님은 많은 걱정을 하며 나를 보내주셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 3개월 정도가 지나 엄마가 시간 여유가 생겨 유럽에 놀러오게 되었고, 내친김에 2개월 넘게 독일 살이를 결정하셨다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엄마와 시간을 보내면서 가장 놀라웠던 것은 엄마의 강인함이다. 워낙 안정성을 추구하시고 소녀같은 분이라 유럽 살이가 괜찮으실 지 걱정했는데, 필요한 것만 몇 개 알려드리니 혼자서 너무 잘다니셔서 놀랐다. 연고도 없는 곳에서 독일 근교로 혼자 1박 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;엄마 덕분에 한식도 엄청 먹었다. 재택할 때마다 오셔서 좋아하는 반찬으로 점심을 차려주시고, 내 친구들을 불러 서너번 저녁을 차려주시기도 했다. 아플때는 삼계탕도 해주셨다. 언어가 잘 통하지 않아 힘드셨을텐데 직접 약국에서 내 증상에 맞는 약을 사와주신 것도 무지 감동이었다. 항상 김치를 사먹어야해서 돈이 많이 들었는데, 내 친구들과 함께 대규모 김장을 해주고 가셔서 4개월 넘게 엄마가 만든 김치로 먹고 살았다. ㅎㅎ 이사도 도와주시고 의자나 자전거와 같이 부피가 있는 중고거래 할때도 같이 가주셨다.&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&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FkbkB/dJMcahb3cbL/NCkJcBLzK2CpG1Q9x5pklk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FkbkB/dJMcahb3cbL/NCkJcBLzK2CpG1Q9x5pklk/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;1712&quot; data-filename=&quot;KakaoTalk_Photo_2026-01-10-23-50-55 001.jpeg&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FkbkB/dJMcahb3cbL/NCkJcBLzK2CpG1Q9x5pklk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFkbkB%2FdJMcahb3cbL%2FNCkJcBLzK2CpG1Q9x5pklk%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;1284&quot; height=&quot;1712&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg6l75/dJMb99LQOk1/WxrrGVSPmoLhu3kj1Jt4s0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg6l75/dJMb99LQOk1/WxrrGVSPmoLhu3kj1Jt4s0/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;1712&quot; data-filename=&quot;KakaoTalk_Photo_2026-01-10-23-50-55 002.jpeg&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg6l75/dJMb99LQOk1/WxrrGVSPmoLhu3kj1Jt4s0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg6l75%2FdJMb99LQOk1%2FWxrrGVSPmoLhu3kj1Jt4s0%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;1284&quot; height=&quot;1712&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 한국으로 보내드릴 때는 조그만 선물과 함께 편지를 써드렸다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;747&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPPm2V/dJMcaiouzPw/ZqHEej1TAkFtYwBccErd6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPPm2V/dJMcaiouzPw/ZqHEej1TAkFtYwBccErd6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPPm2V/dJMcaiouzPw/ZqHEej1TAkFtYwBccErd6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPPm2V%2FdJMcaiouzPw%2FZqHEej1TAkFtYwBccErd6K%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;1600&quot; height=&quot;747&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;747&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자존심 강한 경상도 여자 둘은 공항에서 절대 서로 우는 모습을 보여주지 않았지만, 나는 편지쓰면서도 울었고 엄마 보내고 나서도 울었다 (ㅋㅋ) 엄마도 아마 편지 읽으면서 우시지 않았을까 싶다. 돈 많이 벌어서 더 많은 곳을 여행하게 해드려야겠다고 생각한 시간이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두번의 이사&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해에만 &lt;b&gt;네 번&lt;/b&gt;의 이사를 했다. 와우!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상반기에는 한국 서울 자취방을 정리하고 본가로 내려가는 이사, 그리고 베를린 첫 집에서의 이사.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하반기에는 베를린에서 주거지가 두 번 바뀌어서 두 번의 이사를 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;룸메이트의 아는 언니가 사는 집의 세입자로 들어가 Zelendorf라는 지역에서 5개월 정도를 살았다. 앰뷸런스와 자동차 소리, 술집의 시끄러운 소리가 밤마다 들리던 도시에서 살다가 한적하고 아파트 없이 단독 주택만 있는 남서 지역에 오니 좋았다. 중고 자전거를 사서 타고 다녔고, 여름이면 집 근처 호수에 가서 수영했다. 재택하는 날에는 햇살을 쬐며 마당에서 점심을 먹었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JoMK0/dJMcagc9ESe/G6thTVAIKD2W1G07LeGH91/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JoMK0/dJMcagc9ESe/G6thTVAIKD2W1G07LeGH91/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2026-01-11-00-09-45 004.jpeg&quot; style=&quot;width: 25.855%; margin-right: 10px;&quot; data-widthpercent=&quot;26.47&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JoMK0/dJMcagc9ESe/G6thTVAIKD2W1G07LeGH91/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJoMK0%2FdJMcagc9ESe%2FG6thTVAIKD2W1G07LeGH91%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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rH6L0/dJMcafL4YFE/CqRXaVAYvfPUIW4fHlxk41/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rH6L0/dJMcafL4YFE/CqRXaVAYvfPUIW4fHlxk41/img.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2026-01-11-00-09-35 003.jpeg&quot; style=&quot;width: 45.9644%; margin-right: 10px;&quot; data-widthpercent=&quot;47.06&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rH6L0/dJMcafL4YFE/CqRXaVAYvfPUIW4fHlxk41/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrH6L0%2FdJMcafL4YFE%2FCqRXaVAYvfPUIW4fHlxk41%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;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byJFSm/dJMcafL4YFM/GGTCijKVp2TYF2nXExnKg0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byJFSm/dJMcafL4YFM/GGTCijKVp2TYF2nXExnKg0/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2026-01-11-00-09-45 005.jpeg&quot; style=&quot;width: 25.855%;&quot; data-widthpercent=&quot;26.47&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byJFSm/dJMcafL4YFM/GGTCijKVp2TYF2nXExnKg0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyJFSm%2FdJMcafL4YFM%2FGGTCijKVp2TYF2nXExnKg0%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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIzsTJ/dJMcafL4YFH/ZWYRIzkSkgKLZb3ZZoDNPK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIzsTJ/dJMcafL4YFH/ZWYRIzkSkgKLZb3ZZoDNPK/img.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2026-01-11-00-09-21 001.jpeg&quot; style=&quot;width: 63.2558%; margin-right: 10px;&quot; data-widthpercent=&quot;64&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIzsTJ/dJMcafL4YFH/ZWYRIzkSkgKLZb3ZZoDNPK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIzsTJ%2FdJMcafL4YFH%2FZWYRIzkSkgKLZb3ZZoDNPK%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;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cn9BZM/dJMcacaKz6i/0rIbERab07yP7tobfpOPb1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cn9BZM/dJMcacaKz6i/0rIbERab07yP7tobfpOPb1/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2026-01-11-00-09-25 002.jpeg&quot; data-widthpercent=&quot;36&quot; style=&quot;width: 35.5814%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cn9BZM/dJMcacaKz6i/0rIbERab07yP7tobfpOPb1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcn9BZM%2FdJMcacaKz6i%2F0rIbERab07yP7tobfpOPb1%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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 모종의 이유로 이사를 해야했기 때문에 한국에 다녀오는 2개월 동안에는 새 집을 알아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친구들과 함께 네명이서 살 집을 찾다보니 Lichtenrade라는 지역의 비슷한 결의 2층짜리의 단독 주택으로 집을 구하게되었다. 아직 공사도 끝나지 않은 집이었으나 가격, 회사와의 거리, 집 근처의 분위기를 고려해서 좋은 집이라고 생각해 계약하였다. 공사가 끝난 상태에서 이사하고 나니 정말 집을 잘 구했다고 느꼈다. 바로 옆집에 사는 친절한 집주인분, 예쁜 인테리어, 넓은 거실과 부엌, 마당, 예쁜 뷰 .. 그리고 무엇보다 가구가 다 준비된 상태인게 대박이었다. 저렴한 가격에 너무 좋은 집을 저점매수했다는 것이 뿌듯했다. 그리고 내 방은 2층이고 테라스도 있다 !!!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에는 눈이 많이 왔는데 창밖의 눈 쌓인 풍경이 그렇게 예쁠 수가 없었다. 마음 같아서는 이 집에 오래오래 살고 싶었다. 별 보는 것에 취미 없었는데, 여기 이사와서 별이 너무너무 잘 보여서 별자리를 외우고 다닌다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2026-01-11-00-16-12 004.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pSPMG/dJMcafL4YFK/hIb8CKiP5laO30kquB1hek/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pSPMG/dJMcafL4YFK/hIb8CKiP5laO30kquB1hek/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pSPMG/dJMcafL4YFK/hIb8CKiP5laO30kquB1hek/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpSPMG%2FdJMcafL4YFK%2FhIb8CKiP5laO30kquB1hek%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;4032&quot; height=&quot;3024&quot; data-filename=&quot;KakaoTalk_Photo_2026-01-11-00-16-12 004.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctINzK/dJMcafL4YFI/6c3jK6uaep0RwtN5GXfrK1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctINzK/dJMcafL4YFI/6c3jK6uaep0RwtN5GXfrK1/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2026-01-11-00-16-12 003.jpeg&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctINzK/dJMcafL4YFI/6c3jK6uaep0RwtN5GXfrK1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctINzK%2FdJMcafL4YFI%2F6c3jK6uaep0RwtN5GXfrK1%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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bER9Yh/dJMcafL4YFL/xaaRFwvdtW5ohpZcpegWNK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bER9Yh/dJMcafL4YFL/xaaRFwvdtW5ohpZcpegWNK/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2026-01-11-00-16-08 002.jpeg&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bER9Yh/dJMcafL4YFL/xaaRFwvdtW5ohpZcpegWNK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbER9Yh%2FdJMcafL4YFL%2FxaaRFwvdtW5ohpZcpegWNK%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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1pltB/dJMcagc9ESd/M6Ik2QkIpPcIT3tGbHU3d1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1pltB/dJMcagc9ESd/M6Ik2QkIpPcIT3tGbHU3d1/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2026-01-11-00-16-03 001.jpeg&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1pltB/dJMcagc9ESd/M6Ik2QkIpPcIT3tGbHU3d1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1pltB%2FdJMcagc9ESd%2FM6Ik2QkIpPcIT3tGbHU3d1%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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 지금은 좋은 집에 만족하는 가격으로 살고 있어서 다행이다. 역마살 레전드인 한 해를 보냈다. 이제 이사는 그만하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1인분 하는 개발자 되기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상반기에 어영부영 1인분도 못하던 시절을 생각해보면 그래도 꽤나 성장했던 하반기가 아닐까 싶다.&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;일의 난이도가 쉬워지고 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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;독일어 학원 다니기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;11월부터 2개월 간 꽤나 빡센 독일어 학원을 다녔다. 왜 독일어를 배우는가, 라고 물어보면 딱히 할 말은 없다. 일상에서도 독일어 쓸 일이 많이 없고 회사에서도 영어를 쓰기 때문이다. 하지만 독일에서 독일어 배우는 건 의미있는 일이라고 생각한다. 수업 듣는게 좀 힘들긴 했지만 독일어 자체는 재미있는 것 같다. 식당에서 주문할 때 아주 간단한 독일어로 주문할 수 있는 것도 성과라면 성과다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2026 Q1에 독일어 A1 자격증을 따는 것이 목표이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLL81O/dJMcagc9Fcz/ctqqbodByFXBkNIgcPBzm0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLL81O/dJMcagc9Fcz/ctqqbodByFXBkNIgcPBzm0/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;IMG_1037.JPG&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLL81O/dJMcagc9Fcz/ctqqbodByFXBkNIgcPBzm0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLL81O%2FdJMcagc9Fcz%2FctqqbodByFXBkNIgcPBzm0%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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/booBbm/dJMcafefFl2/HkV1c5IHUbnXqoN3X7WCEk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/booBbm/dJMcafefFl2/HkV1c5IHUbnXqoN3X7WCEk/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;IMG_1109.JPG&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/booBbm/dJMcafefFl2/HkV1c5IHUbnXqoN3X7WCEk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbooBbm%2FdJMcafefFl2%2FHkV1c5IHUbnXqoN3X7WCEk%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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBLiAg/dJMcagc9FcA/ldKDE5FtDqvXztUqgPU3WK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBLiAg/dJMcagc9FcA/ldKDE5FtDqvXztUqgPU3WK/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;IMG_1124.JPG&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBLiAg/dJMcagc9FcA/ldKDE5FtDqvXztUqgPU3WK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBLiAg%2FdJMcagc9FcA%2FldKDE5FtDqvXztUqgPU3WK%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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&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;b&gt;취미 굳히기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상반기에는 사람들과 이것저것 새로운 취미를 많이 시도해봤다면, 하반기에는 내 취미는 이거다! 할 수 있을 정도로 특정 활동에 시간을 많이 들였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 요리이다. 먹는 거 좋아하는 나는 독일에서도 먹고싶은 것이 참 많아서 이것저것 많이 해먹었다. 그러다보니 스스로 오? 싶을만큼 요리 실력이 늘었고 이제 레시피 쓱 참고하고 내 맘대로 계량해서 요리하기도 한다 (!!!) 엄청 큰 변화이다. 사람들 불러서 맛있는 거 대접해주기도 한다. 개발할 때도 혼자서 몰입하는 것이 재미있는데, 요리도 마찬가지이다. 혼자서 노래들으면서 요리하다보면 잡생각이 사라진다. 흑백요리사도 정말 재미있게 보고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 운동이다. 요즘은 매주 수영, 배드민턴을 간다. 수영은 새로 시작했다. 호흡하는 거부터 시작했는데 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;취미라는 걸 모르고 살았는데 직장인 되고 시간 여유가 생기니 내가 좋아하는 게 뭔지 알게 되는 게 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한국 다녀오기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3월 베를린에서 일을 시작한 이후 한국이 너무 그리웠다. 4월 즈음에 8월 말에 한국으로 가는 비행기 티켓을 예약했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막상 한국 갈 때가 되니 독일에서 적응한 일상을 벗어나는 게 아쉽기도 했었다. 한국 가서 보고싶었던 사람들을 잔뜩 만나고 왔다. 나름 독일에서 한식 맛있게 해먹고 다녔다고 생각했는데 한국에서 사먹는 한식은 너무 맛있다. 한국에서만 살 수 있는 화장품이나 겨울 옷들을 구매하고 캐리어에 꽉꽉 채워 들고왔다. 옷, 물건들을 모두 한국 제품으로 사용하고, 소중한 사람들도 다 한국에 있는 상황에서 타국에서 생활한다는 것은 쉽지 않았다. 다시 베를린으로 돌아왔을 때는 타국 생활에 대한 현타를 느끼기도 했다. 나고 자란 국가에서 살고있지 않다는 것은 어쩔 수 없이 조금이라도 결핍이 있는 상태로 지낼 수 밖에 없다는 걸 알게 되었다. 일상을 불편하게 하는 결핍이 있기도 하지만 나를 더 알아가고 성장시키는 결핍도 있다. 하지만 국내든 외국이든 항상 처한 상황에서 결핍은 있기 마련이고 그걸 초연하게 받아들이냐 나를 괴롭히는 요인으로 쓰이냐는 내가 결정할 수 있는 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 한국을 다녀오고 느낀 것은 타국에서의 삶은 힘든 게 당연하고 한국에서 살고 일해도 힘든 게 있었을 거라는 거다. 그래서 타국에서 힘들다는 거를 너무 감정적으로 받아들이지 말고 초연하게 할 일을 하다보면 자연스럽게 앞으로의 길이 펼쳐질 것이라는 생각을 했다는 것을 남기고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다음 해에는,&amp;nbsp;&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;업무 외에도 개인적인 개발 공부에 시간을 많이 투자하고 싶다. 스터디를 하는 것도 좋고, 마감기한을 정해두고 책을 읽는 것도 좋다.&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;</description>
      <category>Other/기록</category>
      <author>MINGYUM</author>
      <guid isPermaLink="true">https://mingyum119.tistory.com/379</guid>
      <comments>https://mingyum119.tistory.com/379#entry379comment</comments>
      <pubDate>Mon, 12 Jan 2026 05:09:38 +0900</pubDate>
    </item>
    <item>
      <title>  왜 OpenFeign과 Circuit Breaker를 함께 사용할까?</title>
      <link>https://mingyum119.tistory.com/378</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mingyum119.tistory.com/376&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.09.12 - [Server/Architecture] -   Resilience4J 라이브러리의 개념과 Circuit Breaker의 동작 과정&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1757676675927&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;  Resilience4J 라이브러리의 개념과 Circuit Breaker의 동작 과정&quot; data-og-description=&quot;Resilience4j 란?fault tolerance (결함 허용) 라이브러리이다. 즉 오류나 장애가 발생해도 서비스가 중단되지 않게 안정적으로 도와주는 서비스이다. 이 기능을 구현하기 위한 여러 Resilience4j 모듈이 있&quot; data-og-host=&quot;mingyum119.tistory.com&quot; data-og-source-url=&quot;https://mingyum119.tistory.com/376&quot; data-og-url=&quot;https://mingyum119.tistory.com/376&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dHlCr1/hyZJp5wbR1/ZOOYhBlwUHdjzmQOC5Qhk1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cS1OaZ/hyZJdEFyCL/E9AVlMx8XvXBM5Pq72r1U1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dtB1jc/hyZIYVYNkY/fOVcWGUEqseSdbwkCl6CjK/img.png?width=616&amp;amp;height=842&amp;amp;face=0_0_616_842&quot;&gt;&lt;a href=&quot;https://mingyum119.tistory.com/376&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mingyum119.tistory.com/376&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dHlCr1/hyZJp5wbR1/ZOOYhBlwUHdjzmQOC5Qhk1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cS1OaZ/hyZJdEFyCL/E9AVlMx8XvXBM5Pq72r1U1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dtB1jc/hyZIYVYNkY/fOVcWGUEqseSdbwkCl6CjK/img.png?width=616&amp;amp;height=842&amp;amp;face=0_0_616_842');&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;  Resilience4J 라이브러리의 개념과 Circuit Breaker의 동작 과정&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Resilience4j 란?fault tolerance (결함 허용) 라이브러리이다. 즉 오류나 장애가 발생해도 서비스가 중단되지 않게 안정적으로 도와주는 서비스이다. 이 기능을 구현하기 위한 여러 Resilience4j 모듈이 있&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mingyum119.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;여기서 어떻게 라이브러리의 목적이 무엇인지 알아보고, 이 목적을 구현하기 위한 여러 모듈 중 Circuit Breaker에 대해 알아보았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mingyum119.tistory.com/377&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.09.12 - [Framework/Spring] - Spring Cloud OpenFeign의 개념과 사용 방법 알아보기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1757685170273&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;Spring Cloud OpenFeign의 개념과 사용 방법 알아보기&quot; data-og-description=&quot;팀 코드를 살펴보다가 외부 서비스와 연동하여 API를 호출하기 위해 @FeignClient 라는 어노테이션을 사용하는 것을 보았다.어떤 원리로 REST API로 서비스와 연동을 하고 있는 지 파악하기 위해 글을 &quot; data-og-host=&quot;mingyum119.tistory.com&quot; data-og-source-url=&quot;https://mingyum119.tistory.com/377&quot; data-og-url=&quot;https://mingyum119.tistory.com/377&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cMlQDr/hyZJddBuh7/TbK663DrwUyhmNylzVenJK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/EDYAa/hyZJdxU8WK/kimTNyR20kz2w8sCF1MWq0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://mingyum119.tistory.com/377&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mingyum119.tistory.com/377&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cMlQDr/hyZJddBuh7/TbK663DrwUyhmNylzVenJK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/EDYAa/hyZJdxU8WK/kimTNyR20kz2w8sCF1MWq0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&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;Spring Cloud OpenFeign의 개념과 사용 방법 알아보기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;팀 코드를 살펴보다가 외부 서비스와 연동하여 API를 호출하기 위해 @FeignClient 라는 어노테이션을 사용하는 것을 보았다.어떤 원리로 REST API로 서비스와 연동을 하고 있는 지 파악하기 위해 글을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mingyum119.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;이어서 Spring Cloud OpenFeign은 외부 서비스와 연동해서 HTTP API 호출을 간단하게 하기 위한 툴이라고 설명하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두가지 라이브러리를 함께 사용하면 외부 서비스에게 HTTP API 호출을 보내는 상황에서 Circuit Breaker를 통해 장애를 탐지하고 자동으로 대응할 수 있다.&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;아래는 Configuration Properties 방식으로 OpenFeign과 Circuit Breaker를 설정한 예제이다.&lt;/p&gt;
&lt;pre id=&quot;code_1757686016261&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  cloud:
    openfeign:
      circuitbreaker:
        enabled: true
        alphanumeric-ids: # 이름에 영문자와 숫자만 사용 가능하도록 하는 옵션
          enabled: true
resilience4j:
  circuitbreaker:
    instances:
      DemoClientgetDemo: # circuit breaker 이름
        minimumNumberOfCalls: 69&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;DemoClientgetDemo라는 이름의 Circuit Breaker에 대한 설정 정보를 입력하였다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1757686673258&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@FeignClient(name = &quot;FeignClientName&quot;)
@CircuitBreaker(name = &quot;DemoClientgetDemo&quot;)
interface HelloClient {
    @RequestMapping(method = RequestMethod.GET, value = &quot;/hello&quot;)
    Hello getHello();
}&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;@CircuitBreaker 어노테이션을 클래스 레벨에 붙이면 모든 메서드에 해당 어노테이션을 적용한 것과 같은 효과를 준다.&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;메서드에 어노테이션을 붙이면 이전 게시글에서 말한 것처럼 모든 함수가 Circuit Breaker에 의해서 Protected function이 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 호출의 결과가 Circuit Breaker에 수집되고 일정한 개수 이상의 실패율이나 일정한 시간 이상 지연 시 CallNotPermittedException이 발생한다.&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;왜 OpenFeign과 Circuit Breaker를 자주 함께 사용할까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 서비스로의 HTTP 요청에 의해서 네트워크 환경이 불안정해지는 사례가 많다.&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;그래서 실패를 감지해 장애 확산을 막아주는 장치가 필요하다. 즉 외부 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;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-clou-feign-circuitbreaker-configurationproperties&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-clou-feign-circuitbreaker-configurationproperties&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1757677057156&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;Spring Cloud OpenFeign&quot; data-og-description=&quot;Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable &quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-clou-feign-circuitbreaker-configurationproperties&quot; data-og-url=&quot;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-clou-feign-circuitbreaker-configurationproperties&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-clou-feign-circuitbreaker-configurationproperties&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-clou-feign-circuitbreaker-configurationproperties&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;Spring Cloud OpenFeign&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Server/Architecture</category>
      <author>MINGYUM</author>
      <guid isPermaLink="true">https://mingyum119.tistory.com/378</guid>
      <comments>https://mingyum119.tistory.com/378#entry378comment</comments>
      <pubDate>Fri, 12 Sep 2025 23:29:29 +0900</pubDate>
    </item>
    <item>
      <title>  Spring Cloud OpenFeign의 개념과 사용 방법 알아보기</title>
      <link>https://mingyum119.tistory.com/377</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;팀 코드를 살펴보다가 외부 서비스와 연동하여 API를 호출하기 위해 @FeignClient 라는 어노테이션을 사용하는 것을 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 원리로 REST API로 서비스와 연동을 하고 있는 지 파악하기 위해 글을 작성한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/OpenFeign/feign&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Feign&lt;/a&gt; 은 HTTP API 호출을 쉽게 해주는 자바 라이브러리이다.&amp;nbsp; Feign 에서 구현된 인터페이스와 어노테이션으로 HTTP API를 호출할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순수한 Feign을 사용해도 좋지만, Spring와 함께 사용해서 DI와 JSON 직렬화/역직렬화 기능을 사용한다면 더욱 확장성 있게 사용할 수 있다. Spring Cloud에서는 Feign을 Spring Boot 환경에서 쓸 수 있게 통합한 모듈로, &lt;b&gt;Spring Cloud OpenFeign&lt;/b&gt; 라이브러리를 도입했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  Spring Cloud란?&lt;br /&gt;Spring Cloud는 마이크로서비스를 지원하는 여러 모듈의 통합된 프로젝트이다.&amp;nbsp; Spring Boot가 개발 애플리케이션을 만드는 역할을 수행한다면, Spring Cloud는 서비스를 운영하기위해 필요한 기능 (로드밸런싱, 게이트웨이 등)을 개발한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Cloud OpenFeign 사용하기&lt;/h3&gt;
&lt;pre id=&quot;code_1757678856086&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootApplication
@EnableFeignClients
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}&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;Spring Boot 애플리케이션에서 @EnableFeignClients 를 사용해 기능을 활성화한다.&lt;/p&gt;
&lt;pre id=&quot;code_1757679125834&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@FeignClient(&quot;stores&quot;)
public interface StoreClient {
    @RequestMapping(method = RequestMethod.GET, value = &quot;/stores&quot;)
    List&amp;lt;Store&amp;gt; getStores();

    @RequestMapping(method = RequestMethod.GET, value = &quot;/stores&quot;)
    Page&amp;lt;Store&amp;gt; getStores(Pageable pageable);

    @RequestMapping(method = RequestMethod.POST, value = &quot;/stores/{storeId}&quot;, consumes = &quot;application/json&quot;)
    Store update(@PathVariable(&quot;storeId&quot;) Long storeId, Store store);

    @RequestMapping(method = RequestMethod.DELETE, value = &quot;/stores/{storeId:\\d+}&quot;)
    void delete(@PathVariable Long storeId);
}&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;인터페이스에 @FeignClient를 통해 'stores' 라는 이름을 가진 Feign 클라이언트를 만들 수 있다. 이 이름을 활용해 Spring Cloud의 인스턴스와 연동해 사용할 수 있게 된다. 인터페이스의 메서드에 의해 실제로 HTTP 요청을 보내는 코드가 런타임에 자동으로 구현된다.&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;혹은 URL을 직접 지정해서 Spring Cloud의 디스커버리를 거치지 않고 바로 해당 주소로 요청을 보낼 수도 있다. 이 경우 Eureka와 같은 서비스 레지스트리가 필요 없고 고정 주소로 바로 거칠 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1757679510029&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@FeignClient(name=&quot;stores&quot;, url=&quot;http://localhost:8080&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;@FeignClient에서 지정하는 이름은 중요하다. 마이크로 서비스에서는 여러 서비스가 동작하는데, 각각 이름으로 인식되기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1757679829275&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
    cloud:
        openfeign:
            client:
                config:
                    feignName:
                        url: http://remote-service.com
                        connectTimeout: 5000
                        readTimeout: 5000
                        loggerLevel: full
                        errorDecoder: com.example.SimpleErrorDecoder
                        retryer: com.example.SimpleRetryer
                        defaultQueryParameters:
                            query: queryValue
                        defaultRequestHeaders:
                            header: headerValue
                        requestInterceptors:
                            - com.example.FooRequestInterceptor
                            - com.example.BarRequestInterceptor
                        responseInterceptor: com.example.BazResponseInterceptor
                        dismiss404: false
                        encoder: com.example.SimpleEncoder
                        decoder: com.example.SimpleDecoder
                        contract: com.example.SimpleContract
                        capabilities:
                            - com.example.FooCapability
                            - com.example.BarCapability
                        queryMapEncoder: com.example.SimpleQueryMapEncoder
                        micrometer.enabled: false&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;configuration properties를 사용해서 설정하는 방식도 있다. 여기서 feignName은 @FeignClient에 지정된 이름과 일치해야한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 OpenFeign으로 등록한 클라이언트 서비스를 Circuit Breaker에 활용하는 방법을 알아보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1757677121051&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;Spring Cloud OpenFeign&quot; data-og-description=&quot;Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable &quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign&quot; data-og-url=&quot;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign&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;Spring Cloud OpenFeign&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Framework/Spring</category>
      <author>MINGYUM</author>
      <guid isPermaLink="true">https://mingyum119.tistory.com/377</guid>
      <comments>https://mingyum119.tistory.com/377#entry377comment</comments>
      <pubDate>Fri, 12 Sep 2025 22:36:40 +0900</pubDate>
    </item>
    <item>
      <title>  Resilience4J 라이브러리의 개념과 Circuit Breaker의 동작 과정</title>
      <link>https://mingyum119.tistory.com/376</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Resilience4j&lt;/b&gt; 란?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fault tolerance (결함 허용) 라이브러리이다. 즉 오류나 장애가 발생해도 서비스가 중단되지 않게 안정적으로 도와주는 서비스이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;이 기능을 구현하기 위한 여러 Resilience4j 모듈이 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장애가 전파되지 않도록 차단하는 CircuitBreaker&lt;/li&gt;
&lt;li&gt;실패 시 자동 재시작으로 장애 극복을 시도하는 Retry&lt;/li&gt;
&lt;li&gt;결과를 캐시해 장애가 나더라도 결과를 전달하게 하는 Cacjhe&lt;/li&gt;
&lt;li&gt;갑작스럽게 트래픽이 폭증한 상황에서 요청량을 제어하는 RateLimiter&lt;/li&gt;
&lt;li&gt;자원을 배분해서 장애가 나도 다른 서비스에서 작동하도록 하는 BulkHead&lt;/li&gt;
&lt;li&gt;응답시간 초과 시 타임아웃으로 지연 장애가 전파되지 않도록 하는 TimeLimiter&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스팅에서는 Circuit Breaker 모듈에 집중해서 알아보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Circuit Breaker&lt;/b&gt;란?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Finite State Machine (유한 상태 기계) 개념을 사용해 구현되었다. 즉 정해져있는 세가지의 상태에 따라 입력을 처리하고 다음 상태로 전환한다.&lt;/li&gt;
&lt;li&gt;CLOSED, OPEN, HALF_OPEN 세가지 상태가 기본이고 특수한 상황에 METRICS_ONLY, DISABLED, FORCED_OPEN 상태를 가지고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1uFFe/btsQwLEgwAw/OR1ACIWucgpgIK71JCgtkk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1uFFe/btsQwLEgwAw/OR1ACIWucgpgIK71JCgtkk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1uFFe/btsQwLEgwAw/OR1ACIWucgpgIK71JCgtkk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1uFFe%2FbtsQwLEgwAw%2FOR1ACIWucgpgIK71JCgtkk%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;426&quot; height=&quot;164&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 상태로 전환하는 전이 규칙은 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1013&quot; data-start=&quot;987&quot;&gt;CLOSED &amp;rarr; OPEN (실패율 초과): 요청이 여러번 실패하거나 지연이 너무 긴 경우 차단기가 실행됨 (OPEN)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1013&quot; data-start=&quot;987&quot;&gt;상태의 변화는 일정한 Threshold를 기준으로 한다. 실패율이나 지연율이 Threshold 이상이라면 실패라고 간주하고 OPEN 상태로 전환한다. 실패율의 경우 실패라고 간주할 수 있는 예외의 목록을 따로 설정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1053&quot; data-start=&quot;1016&quot;&gt;OPEN &amp;rarr; HALF_OPEN (타이머 만료 후 시험 호출): 일정 시간이 지난 뒤 제한적으로 호출을 허용함 (HALF_OPEN)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1053&quot; data-start=&quot;1016&quot;&gt;차단기가 실행된 이후 서버가 활성화되어있는 지 시험해보기 위해 설정한 만큼의 요청을 테스트로 받아보는 상태로 전환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1101&quot; data-start=&quot;1056&quot;&gt;HALF_OPEN &amp;rarr; CLOSED (시험 성공) / OPEN (시험 실패): 제한적으로 호출을 허용하여 테스트한 뒤 요청이 성공하면 차단기를 끄고 (CLOSED) 실패하면 차단기를 다시 실행한다 (OPEN)&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;그렇다면 Circuit Breaker가 &lt;b&gt;어떻게 장애 상태를 파악&lt;/b&gt;할 수 있을까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패율과 지연율을 측정하기 위해서는 요청의 결과를 지속적으로 수집하고 관찰해야한다. Circuit Breaker에서는 이를 위해 두 가지 방식을 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Count-based Sliding Window&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 N개의 요청의 결과를 모은다. 윈도우 크기가 10이라면 요청의 개수는 항상 10개이다.&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;이 시간복잡도는 O(1)이고 공간복잡도는 O(N)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Time-based Sliding Window&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 N초의 요청 결과를 모은다. 1초를 하나의 '버킷'이라는 개념으로 생각하고, 한 버킷마다 생긴 요청의 결과를 저장해둔다. 예를 들어 윈도우 크기가 10이라면 각 1초 동안 생긴 요청의 결과를 각 버킷에 저장한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1757653786909&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bucket[0] = 3 success / 1 fail
bucket[1] = 1 success / 0 fail
bucket[2] = 2 success / 1 fail
...&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;그래서 윈도우가 이동할 때마다 (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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;여러 스레드&lt;/b&gt;에서 Circuit Breaker의 상태를 변경한다면?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Circuit Breaker는 하나의 객체로 힙 메모리에서 단 하나의 상태를 가진다. 하지만 여러 스레드가 JVM에서 동작하면서 Circuit Breaker의 상태를 동시에 변경할 수 있다. 이 위험을 방지하기 위해서 Circuit Breaker는 기본적으로 Thread-safe한 방식으로 구현되었다.&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;상태는 AtomicReference에 저장된다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;원자적 연산 (Atomic Operations)를 사용해 상태를 갱신한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;슬라이딩 윈도우에서 요청을 읽고 계산하는 작업은 Synchronized 상태에서 진행된다.&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;하지만 애플리케이션 함수 호출 자체를 동기화하지 않는다. Circuit Breaker에 따른 큰 성능 저하를 막기 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 호출 자체를 동기화하기 위해서는 BulkHead를 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kXSQj/btsQvyMXo9u/JyMjtdKCCT1nQ7F9cjAkFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kXSQj/btsQvyMXo9u/JyMjtdKCCT1nQ7F9cjAkFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kXSQj/btsQvyMXo9u/JyMjtdKCCT1nQ7F9cjAkFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkXSQj%2FbtsQvyMXo9u%2FJyMjtdKCCT1nQ7F9cjAkFk%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;616&quot; height=&quot;842&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 사진은 세 개의 스레드가 동시에 CircuitBreaker 에 접근해서 함수호출을 시도한 경우이다. 복잡해보이지만, 아래 두가지 프로세스에 기반해서 이해해볼 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드가 Circuit Breaker를 호출했을 때 CLOSED 상태인 경우 true를 스레드에게 반환하고, 스레드는 Protected function을 호출한다.&lt;/li&gt;
&lt;li&gt;Protected function에서 예외가 발생한 경우 스레드에 예외가 반환되고, 스레드는 onError(exception) 함수를 통해 CircuitBreaker에 오류가 발생한 사실을 알린다.&lt;/li&gt;
&lt;li&gt;예외가 일정횟수를 넘기면 CallNotPermittedException가 발생되고 CircuitBreaker가 OPEN 상태로 전환된다. 이 경우 스레드가 CircuitBreaker를 통해 Protected function에 접근을 요청해도 예외를 반환한다.&lt;/li&gt;
&lt;/ul&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;여러 스레드가 동시에 Circuit Breaker를 통해 protected function을 호출할 수 있다. 즉, protected function을 호출하는 것은 동기화되지 않는다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;T2의 요청이 진행 중인 사이에 T1에 오류가 생겨서 임계값이 도달하였다. T2의 요청이 여전히 진행 중임에도 불구하고 T3이 요청하였을 때 임계값 도달에 의해서 바로 CallNotPermittedException 예외를 던진다. 이에 따라 스레드가 protected function을 동기화해서 호출하지는 않지만 예외를 집계하고 상태를 전환하는 것은 atomic하게 진행된다는 것을 알 수 있다.&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;다음에는 OpenFeign에서 Resilience4J를 사용하는 방식에 대해서 알아보겠다.&lt;/p&gt;</description>
      <category>Server/Architecture</category>
      <author>MINGYUM</author>
      <guid isPermaLink="true">https://mingyum119.tistory.com/376</guid>
      <comments>https://mingyum119.tistory.com/376#entry376comment</comments>
      <pubDate>Fri, 12 Sep 2025 15:02:43 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] runBlocking을 사용해 코루틴 함수 호출하기</title>
      <link>https://mingyum119.tistory.com/375</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무 중 다른 팀이 개발한 프로젝트를 라이브러리로 가져와 사용할 일이 있었다. 메서드를 사용하던 와중에 처음 보는 컴파일 에러를 발견했다.&lt;/p&gt;
&lt;pre id=&quot;code_1755722166422&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Suspend function 'suspend fun test()' can only be called from a coroutine or another suspend function.&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;호출한 메서드가 suspend 함수이기 때문에 코루틴이나 다른 suspend 함수 내에서만 호출 가능하다는 문구이다.&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;suspend 함수란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴에서 &lt;b&gt;함수를 실행할 때 도중에 멈출 수 있음&lt;/b&gt;을 표시하기 위해 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 실행 중 멈추는 경우에 스레드를 점유하며 성능이 저하되는 현상을 방지하기 위해, suspend 함수에서는 함수가 멈춰있는 동안 해당 스레드를 다른 코루틴 프로세스가 점유할 수 있도록 한다.&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://kotlinlang.org/docs/composing-suspending-functions.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kotlinlang.org/docs/composing-suspending-functions.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1755723028735&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;Composing suspending functions | Kotlin&quot; data-og-description=&quot; &quot; data-og-host=&quot;kotlinlang.org&quot; data-og-source-url=&quot;https://kotlinlang.org/docs/composing-suspending-functions.html&quot; data-og-url=&quot;https://kotlinlang.org/docs/composing-suspending-functions.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/TeYCi/hyZynhDKso/MZDiLnyIvQb8c7Jhz8BNkk/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/docs/composing-suspending-functions.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kotlinlang.org/docs/composing-suspending-functions.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/TeYCi/hyZynhDKso/MZDiLnyIvQb8c7Jhz8BNkk/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800');&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;Composing suspending functions | Kotlin&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kotlinlang.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;suspend 함수를 호출하는 모든 함수도 마찬가지로 실행 도중 멈출 가능성이 있기 때문에, 연쇄적으로 모두 suspend 함수로 선언해주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;74&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ces0h/btsP1tx05yj/pzAuQShOF1w1QcIp51YXv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ces0h/btsP1tx05yj/pzAuQShOF1w1QcIp51YXv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ces0h/btsP1tx05yj/pzAuQShOF1w1QcIp51YXv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCes0h%2FbtsP1tx05yj%2FpzAuQShOF1w1QcIp51YXv1%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;1460&quot; height=&quot;74&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;74&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;runBlocking&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1812&quot; data-origin-height=&quot;1082&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqM7SG/btsQgQfGBO6/85LZxXu0qebe7t8wPz4Wf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqM7SG/btsQgQfGBO6/85LZxXu0qebe7t8wPz4Wf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqM7SG/btsQgQfGBO6/85LZxXu0qebe7t8wPz4Wf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqM7SG%2FbtsQgQfGBO6%2F85LZxXu0qebe7t8wPz4Wf1%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;1812&quot; height=&quot;1082&quot; data-origin-width=&quot;1812&quot; data-origin-height=&quot;1082&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;suspend 함수를 사용하는 방법 중 하나는 runBlocking 스코프 내에서 호출하는 것이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Runs a new coroutine and blocks the current thread interruptibly until its completion&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;runBlocking은 코루틴을&amp;nbsp;&lt;b&gt;동기적으로 실행하는 함수&lt;/b&gt;이다. 즉 runBlocking 블록 안에 있는 함수를 위해 코루틴 스레드를 생성하고, 현재 스레드를&amp;nbsp;&lt;b&gt;해당 코루틴 스레드가 끝날 때까지 &lt;/b&gt;블로킹하는 역할이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;runBlocking 스코프 내에서 suspend 함수가 실행될 때 실행 도중 잠깐 멈춘다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 suspend 함수를 선언하는 목적은 함수가 중간에 멈추더라도 스레드를 점유하지 않고 다른 프로세스가 사용할 수 있도록 하기 위함이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 runBlocking을 쓰면 동기로 실행되기 때문에 다른 프로세스가 이 스레드를 사용할 수 없다.&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;suspend 연쇄적으로 사용 vs runBlocking&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;suspend로 선언된 함수를 사용할 때 두 가지 선택지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째로는 모든 호출 함수를 연쇄적으로 suspend 선언하는 것, 두 번째로는 runBlocking을 사용해서 현재 스레드를 멈추고 코루틴 스레드를 실행하는 것.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 방법의 장점으로는 현재 스레드를 Blocking하지 않기 때문에 처리량이 늘고 지연이 적어진다. 그러나 연쇄적인 suspend 함수 사용으로 리팩터링 범위가 커진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 방법의 장점은 코틀린과 같이 동기 프로그래밍을 사용하는 경우, 굳이 비동기 프로그래밍 세계로 진입하지 않고도 suspend 함수를 사용할 수 있다. 모든 호출 함수를 suspend로 변경하지 않아도 되어서 간단하다. 하지만 호출 스레드를 블로킹하기 때문에 처리량이 느려질 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성능에 문제가 없을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;runBlocking을 사용하여 suspend 함수 호출 도중 멈추게 된다면, 스레드를 해당 프로세스가 계속 점유하게 되니 프로덕션의 성능에 문제가 생기지 않을까?&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;동기 방식으로 스레드를 지속적으로 잡아놨을 때, suspend 함수에 의해 스레드가 점유된다면 문제가 생길 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 블로킹이 문제가 됨을 확인하기 위해서, 배포 이전에 프로덕션과 유사한 환경에서 부하 상황을 시뮬레이션하고 활성 스레드 수가 급증하는지, 혹은 대기작업 큐 길이가 증가하는지 관찰할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Language/Kotlin</category>
      <author>MINGYUM</author>
      <guid isPermaLink="true">https://mingyum119.tistory.com/375</guid>
      <comments>https://mingyum119.tistory.com/375#entry375comment</comments>
      <pubDate>Tue, 2 Sep 2025 15:23:40 +0900</pubDate>
    </item>
    <item>
      <title>이벤트 발행/구독 모델을 활용한 실시간 데이터 동기화 feat. AWS SNS, SQS</title>
      <link>https://mingyum119.tistory.com/374</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  프로젝트 목표&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 기술회고에서 우리 서비스가 다른 서비스와 &lt;b&gt;실시간으로 데이터가 동기화&lt;/b&gt;되고 있다는 언급을 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔 예측하지 못한 이유로 이러한 동기화가 제대로 작동하지 않는 경우가 있다. 빠른 후속조치를 위해 상태 변화 전송이 누락된 사용자에 대해 이벤트를 발행하고 데이터를 완전히 동기화시키는 기능이 필요해졌다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 아이디의 리스트를 받아 해당 사용자의 상태 변화를 트래킹 할 수 있는 이벤트를 전송하는 것을 목표로 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  구현 방식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;이벤트가 타 서비스에서 잘 소비되고 있는 지 확인한다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;⚒️ 기술 스택&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 이벤트 발행과 구독, 소비하는 패턴의 흐름을 중점적으로 다루는 글이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 프로젝트의 이벤트 발행 - 구독 시스템은 AWS SNS, SQS를 사용한다.&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;2094&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rxUFD/btsPFgSYSQN/pzavnwcTtbN6XXQc7luh30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rxUFD/btsPFgSYSQN/pzavnwcTtbN6XXQc7luh30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rxUFD/btsPFgSYSQN/pzavnwcTtbN6XXQc7luh30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrxUFD%2FbtsPFgSYSQN%2FpzavnwcTtbN6XXQc7luh30%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;2094&quot; height=&quot;512&quot; data-origin-width=&quot;2094&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SNS (Simple Notification Service)&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지를 여러 구독자에게 발행하기 위해 사용하는 AWS 서비스이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;이 메시지를 구독하는 구독자는 SQS 큐뿐만 아니라 이메일, HTTP 등 다양한 형태가 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;SNS Topic은 구독자가 구독하는 하나의 단위 (채널)이다. 슬랙이 SNS처럼 하나의 시스템이라면, 각 슬랙 채널은 SNS Topic에 해당한다. 해당 채널에 관심이 있는 사람 (구독자)만 해당 채널에 들어가는 것처럼, 구독도 SNS Topic 단위로 이루어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SQS (Simple Queue Service)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지를 큐에 저장하고 비동기로 메시지를 Consumer가 꺼내가기 위해 사용하는 AWS 서비스이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;특정 SNS Topic을 구독하고, 메시지가 발행되어 큐에 저장되었을 때 별도의 서비스에서 메시지를 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 SNS, SQS를 사용하는가?&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적으로는 사실 SNS나 SQS가 없어도 이벤트 발행과 구독이 가능하다. HTTP 형태로 다른 서비스에 직접 데이터를 Push할 수 있고, 혹은 제3의 Gateway에 데이터를 보내놓은 다음 다른 서비스가 원할 때 Pull 방식으로 가져갈 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 왜 SNS, SQS 를 사용해야할까? 바로 가용성 (Scalable) 때문이다.&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;1214&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLcvvg/btsPFYEn9ts/kZkmquR9Si4oVyZ1ZQ5awK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLcvvg/btsPFYEn9ts/kZkmquR9Si4oVyZ1ZQ5awK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLcvvg/btsPFYEn9ts/kZkmquR9Si4oVyZ1ZQ5awK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLcvvg%2FbtsPFYEn9ts%2FkZkmquR9Si4oVyZ1ZQ5awK%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;474&quot; height=&quot;289&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 여러 서비스가 우리 서비스에 의존하게 된다면, 의존하는 서비스가 늘 수록 우리 서비스의 변경 지점이 많아진다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmgRIY/btsPGl0nttM/f0JbJyuv2UC8AW62HCgAv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmgRIY/btsPGl0nttM/f0JbJyuv2UC8AW62HCgAv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmgRIY/btsPGl0nttM/f0JbJyuv2UC8AW62HCgAv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmgRIY%2FbtsPGl0nttM%2Ff0JbJyuv2UC8AW62HCgAv0%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;536&quot; height=&quot;260&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;790&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책임을 SNS가 지게되면 어떨까? SNS가 구독자를 직접 정의해야하긴 하지만, 우리 서비스와의 의존성은 분리되므로 괜찮아보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 SNS Topic이 여러 개가 되면 어떨까?&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;1710&quot; data-origin-height=&quot;1150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MvQMu/btsPF4Lk8yq/ZWteLsr8eMShP21IRXVN21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MvQMu/btsPF4Lk8yq/ZWteLsr8eMShP21IRXVN21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MvQMu/btsPF4Lk8yq/ZWteLsr8eMShP21IRXVN21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMvQMu%2FbtsPF4Lk8yq%2FZWteLsr8eMShP21IRXVN21%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;556&quot; height=&quot;374&quot; data-origin-width=&quot;1710&quot; data-origin-height=&quot;1150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 SNS Topic마다 중복된 서비스를 직접 등록해야하는 일이 생길 것이다. 이벤트 구독의 유무가 변할 때마다 중복된 정보를 수정해주어야하는 비용이 생긴다.&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;2364&quot; data-origin-height=&quot;1066&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FDvyv/btsPDpcI6U2/7Kk1fytkRWWL6crknXYJnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FDvyv/btsPDpcI6U2/7Kk1fytkRWWL6crknXYJnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FDvyv/btsPDpcI6U2/7Kk1fytkRWWL6crknXYJnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFDvyv%2FbtsPDpcI6U2%2F7Kk1fytkRWWL6crknXYJnK%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;663&quot; height=&quot;299&quot; data-origin-width=&quot;2364&quot; data-origin-height=&quot;1066&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;이렇게 SNS와 SQS의 조합을 사용하면&amp;nbsp;&lt;b&gt;상대적으로 자주 변하는 이벤트 발행・구독 정보&lt;/b&gt;에 최소화된 변경 지점으로 아키텍처를 수정할 수 있게 된다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;zwj;  구현 과정&lt;/b&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;a href=&quot;https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/sns/AmazonSNS.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AmazonSNS&lt;/a&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;910&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ci3bX2/btsPEO3VnTq/3J0XoV6KAhT9kkyeG4clI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ci3bX2/btsPEO3VnTq/3J0XoV6KAhT9kkyeG4clI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ci3bX2/btsPEO3VnTq/3J0XoV6KAhT9kkyeG4clI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fci3bX2%2FbtsPEO3VnTq%2F3J0XoV6KAhT9kkyeG4clI0%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;598&quot; height=&quot;139&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;topicArn: 전송할 SNS 토픽의 이름이다.&lt;/li&gt;
&lt;li&gt;message: 전송할 이벤트의 내용을 문자열 형태로 담는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 토픽과, 메시지를 담아서 해당 함수를 호출해주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-08-03 at 21.49.24.png&quot; data-origin-width=&quot;2392&quot; data-origin-height=&quot;1270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVEZMF/btsPERszhfD/9XombwexXqGXM4uHfQJ6wK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVEZMF/btsPERszhfD/9XombwexXqGXM4uHfQJ6wK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVEZMF/btsPERszhfD/9XombwexXqGXM4uHfQJ6wK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVEZMF%2FbtsPERszhfD%2F9XombwexXqGXM4uHfQJ6wK%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;2392&quot; height=&quot;1270&quot; data-filename=&quot;Screenshot 2025-08-03 at 21.49.24.png&quot; data-origin-width=&quot;2392&quot; data-origin-height=&quot;1270&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;AWS SNS Topic 페이지를 들어가면 ARN을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 Subscriptions 탭에서는 SNS에 구독 중인 SQS의 목록을 확인할 수 있다.&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;Subscription 중 하나를 클릭하면 SQS의 상세 정보를 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-08-03 at 21.52.40.png&quot; data-origin-width=&quot;2394&quot; data-origin-height=&quot;1306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6g23D/btsPEEUviMo/cTcbaOqKRaEec5FmYpGA7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6g23D/btsPEEUviMo/cTcbaOqKRaEec5FmYpGA7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6g23D/btsPEEUviMo/cTcbaOqKRaEec5FmYpGA7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6g23D%2FbtsPEEUviMo%2FcTcbaOqKRaEec5FmYpGA7K%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;2394&quot; height=&quot;1306&quot; data-filename=&quot;Screenshot 2025-08-03 at 21.52.40.png&quot; data-origin-width=&quot;2394&quot; data-origin-height=&quot;1306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발행된 메시지를 구독하는 쪽에서는 어떻게 구현할 수 있을까?&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;pre id=&quot;code_1754251292070&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# application.yml 
eventbus: 
	env_name_of_queue: queue_name_in_sqs&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;@JmsListener를 사용하면 JMS 메시지를 리스닝하는 메시지를 정의할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JMS 메시지란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java Message Service라는 뜻, 자바 어플리케이션에서 메시지를 생성하거나 읽을 수 있는 기능을 지원한다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@JmsListener(destination = &quot;\${eventbus.env_name_of_queue}&quot;)
fun listen(message: SQSTextMessage): Nothing? = listen&amp;lt;MessageHolder&amp;gt;(message) { 
   	// do something
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 JSON 형태로 보낸 메시지를 MessageHolder라는 객체로 전달받을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@JmsListener의 destination에는 이 함수가 수신 대기해야하는 대상의 이름이 들어간다.&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;JmsListenerContainer가 내부 리스너를 생성해 큐를 구독한다. @JmsListener는 &lt;b&gt;내&lt;/b&gt;부적으로 JMS 메시지를 지속적으로 감시하는 리스너 컨테이너를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리스너 컨테이너가 필요한 이유?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백그라운드에서 리스너가 계속 돌아가고 있어야 등록된 desitnation, 즉 큐에 이벤트가 생성되었을 때 소비할 수 있다.&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;이렇게 이벤트 발행하는 서비스에서는 AmazonSns 라이브러리를 사용해서,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SNS Topic을 구독하고 있는 서비스에서는 JMS 라이브러리를 사용해 이벤트를 백그라운드에서 수신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✏️ 느낀 점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 팀 코드 베이스에서&amp;nbsp;&lt;b&gt;어떻게 이벤트를 전달하고, 외부 서비스에서 어떻게 우리 이벤트를 소비하는 지&lt;/b&gt; 이해할 수 있게 된 프로젝트였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 이벤트 발행/구독 모델과 AWS 서비스 사용에 익숙해질 수 있었다. 해당 태스크를 진행하면서 시니어 개발자분이 이벤트 발행/구독 구조에 대해 설명해주시고, 왜 이런 모델이 필요한지 설명해주셨다. 당연하게 받아들이고 있던 것에 '왜'라는 질문을 던지는 자세가 중요함을 한번 더 인지할 수 있었다. 단순히 이벤트를 전달하는 API를 추가하는 것에서 끝나는 게 아니라, 다른 서비스에서 어떻게 이벤트를 전달받고 있는 지 알아보는 기회가 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✏️ 참고&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://spring.io/guides/gs/messaging-jms&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://spring.io/guides/gs/messaging-jms&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754252114006&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;Getting Started | Messaging with JMS&quot; data-og-description=&quot;You will build an application that uses Spring&amp;rsquo;s JmsTemplate to post a single message and subscribes to it with a @JmsListener annotated method of a managed bean.&quot; data-og-host=&quot;spring.io&quot; data-og-source-url=&quot;https://spring.io/guides/gs/messaging-jms&quot; data-og-url=&quot;https://spring.io/guides/gs/messaging-jms&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/saJIf/hyZuzVbaa3/QmqxyWnwM8FSzvG1CUlO81/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bMrO7c/hyZviTefOO/DdV5u5pwHUx8dKztZojWi0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://spring.io/guides/gs/messaging-jms&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://spring.io/guides/gs/messaging-jms&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/saJIf/hyZuzVbaa3/QmqxyWnwM8FSzvG1CUlO81/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bMrO7c/hyZviTefOO/DdV5u5pwHUx8dKztZojWi0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Getting Started | Messaging with JMS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;You will build an application that uses Spring&amp;rsquo;s JmsTemplate to post a single message and subscribes to it with a @JmsListener annotated method of a managed bean.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>업무/기술회고</category>
      <author>MINGYUM</author>
      <guid isPermaLink="true">https://mingyum119.tistory.com/374</guid>
      <comments>https://mingyum119.tistory.com/374#entry374comment</comments>
      <pubDate>Mon, 4 Aug 2025 05:48:01 +0900</pubDate>
    </item>
    <item>
      <title>Prometheus &amp;amp; Grafana &amp;amp; Micrometer 를 사용한 지표 생성과 알림 추가</title>
      <link>https://mingyum119.tistory.com/373</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  프로젝트 목표&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 서비스에서 사용자에게 제공하는 비즈니스 로직으로 인해, 우리 서비스 내부에서만 확인할 수 있는 데이터가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 데이터를 활용해 제공할 수 있는 또 다른 기능이 다른 서비스로 확장되면서 다른 서비스에서 우리 서비스와 &lt;b&gt;데이터를 동기화 할 필요&lt;/b&gt;가 생겼다. 그래서 이벤트를 발행해 우리 서비스 데이터의 새로운 업데이트가 생기면 다른 서비스에서 구독해서 데이터를 동기화하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 흐름에서, 두 서비스의 데이터가 일치하지 않으면 &lt;b&gt;즉각 발견하고 대응할 필요&lt;/b&gt;가 생겼다. 이 니즈를 해결하기 위해 알림 시스템을 사용하고, 알림을 발생시키기 위한 지표를 수집해야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 따라 &lt;b&gt;지속적으로 현재 우리 서비스의 상태값과 해당 서비스의 상태값이 일치하는 지 확인하는 업무&lt;/b&gt;를 맡게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  구현 방식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림을 발생시키기 위해서는 알림을 트리거 할 '조건'이 필요하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건을 만들기 위해서는 지속적으로 현재 상태를 모니터링할 수 있는 '지표'가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 아래 흐름으로 구현을 하는 것을 계획하였다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;1. 우리 서비스의 상태 값을 모니터링하는 지표를 생성한다.&amp;nbsp;&lt;br /&gt;2. 우리 서비스와 다른 서비스의 데이터가 불일치함을 감지할 수 있는 조건을 만든다.&amp;nbsp;&lt;br /&gt;3. 조건이 트리거되었을 때 알림을 발생할 수 있도록 설정한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;⚒️ 기술 스택&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2618&quot; data-origin-height=&quot;946&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWOQzs/btsPDC3mnkS/ejRzj7fDfz62Lvbb1R9AsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWOQzs/btsPDC3mnkS/ejRzj7fDfz62Lvbb1R9AsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWOQzs/btsPDC3mnkS/ejRzj7fDfz62Lvbb1R9AsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWOQzs%2FbtsPDC3mnkS%2FejRzj7fDfz62Lvbb1R9AsK%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;2618&quot; height=&quot;946&quot; data-origin-width=&quot;2618&quot; data-origin-height=&quot;946&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Backend Application &lt;/b&gt;데이터베이스와 연결되어 필요한 형태로 데이터를 수집하고 가공한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Micrometer &lt;/b&gt;백엔드 애플리케이션과 연결되어 가공된 데이터를 프로메테우스로 전송하는 역할을 한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Prometheus &lt;/b&gt;마이크로미터와 연결되어 데이터를 지표로서 수집하고 그라파나로 전송한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Grafana &lt;/b&gt;전송된 지표와 설정된 알림을 시각적으로 보여준다. 불일치가 발생하였을 때 자동으로 이를 트리거하고 알림을 발생시킬 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;zwj;  구현 과정&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;도메인을 이해하고 지표로 보낼 데이터 만들기&lt;/b&gt;&lt;/h4&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;한 명의 사용자가 상태 A에서 상태 B로 전환된 상황에서 이벤트가 발행된다고 가정하면, 상태 B인 사용자가 총 몇 명인지 확인할 수 있는 데이터베이스 쿼리를 디자인하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 '&lt;b&gt;상태 B인 사용자 수&lt;/b&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;b&gt;Micrometer를 사용해 Prometheus로 지표 전송하는 코드 작성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 데이터베이스 쿼리로 수집한 값을 지표로 전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Micrometer는 MeterRegistry를 사용해 애플리케이션 내에 지표를 관리한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MeterRegistry를 빈 등록하고 아래 세 가지 옵션 중 하나를 선택해서 지표를 추가할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1754083152310&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Counter counter = registry.counter(&quot;counter&quot;); 
AtomicInteger myGauge = registry.gauge(&quot;numberGauge&quot;, new AtomicInteger(0));
Timer timer = Timer.builder(&quot;my.timer&quot;).register(registry);&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;지표의 성격에 따라 Counter, Gauge, Timer 중에 하나를 선택해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Counter는 일정하게 증가하는 성격의 지표일 때. 예를 들어 총 배달 횟수와 같이 항상 양수이며 증가만 하는 지표이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Gauge는 현재 상태를 모니터링하는 성격의 지표일때. 예를 들어 현재 접속사 수와 같이 증가하고 줄어들기도 하는 지표이다.&lt;/li&gt;
&lt;li&gt;Timer는 짧은 시간의 Latency를 측정하거나 특정 이벤트의 주기를 계산하기 위한 지표이다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특정 상태인 사용자의 총&lt;/b&gt;&lt;b&gt;합&lt;/b&gt;을 지표로 사용하고 있기 때문에, 사용자마다 상태가 지속적으로 변화함에 따라 &lt;b&gt;Gauge&lt;/b&gt; 지표를 사용해 현재 총 합을 모니터링할 필요가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 기존 지표들은 모두 Counter 지표였으며 이를 기반해 설계되어있었다. 그래서 Gauge 지표를 전송하기 위한 시스템을 도입해야했고, 다루기 까다로웠던 포인트가 세가지 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &amp;zwj;♀️ 메모리를 모니터링해서 지표 값을 업데이트하는 Gauge의 특이한 시스템&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1754083893269&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;meterRegistry.counter(metricName, tags).increment(amount)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Counter의 경우 해당 코드를 반복호출해서 일정 amount만큼 증가하였음을 지표로 전송하는 시스템이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 &lt;b&gt;Gauge의 경우 Micrometer가 지속적으로 애플리케이션 메모리를 모니터링&lt;/b&gt;하고, 메모리 값의 변화가 생기면 Prometheus로 바뀐 값을 전송한다. 이를 하이엔-게이지 (Heisen-gauge) 라고 한다. 코드로 설명해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1754084102064&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private val gaugeMetric = AtomicDouble()&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;Micrometer가 모니터링할 메모리를 할당하기 위해 변수를 선언했다.&lt;/p&gt;
&lt;pre id=&quot;code_1754084202466&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;meterRegistry.gauge(metricName, tags, amount)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 변수가 앞으로 모니터링될 수 있게 gauge 함수를 사용해서 Micrometer에 등록한다. 이제 이 변수는 애플리케이션이 살아있는 동안 모니터링된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754084276286&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;gaugeMetric.increment()
gaugeMetric.decrement()&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; 혹은 아예 새로운 값으로 변경하고 싶다면 아래와 같이 변수의 값을 바꾼다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754084422140&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;gaugeMetric.set(20)&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;이렇게 하면 자동으로 Micrometer가 변화를 감지해 Prometheus에게 변화된 지표를 전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &amp;zwj;♀️ 원시값이 아닌 객체를 사용해서 변수를 선언해야하는 점&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;Gauge 지표를 사용할 때는 변수를 Double, Integer 처럼 원시값이 아닌 &lt;b&gt;AtomicDouble, AtomicInteger을 사용&lt;/b&gt;해주어야 한다. 왜냐하면 원시값의 값을 참조하는 것이 아니라, 객체의 주소를 참조해야하기 때문이다. 원시값으로 복사해서 등록하게 되면 값의 변경을 감지할 수 없다. 하지만 주소를 참조한다면 값의 변경을 감지해 지표를 갱신할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &amp;zwj;♀️ 애플리케이션 재시작마다 지표가 초기화된다는 점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 시점에서 지표를 전송할 것이냐? 라는 고민을 하였다. 원래 계획은 &lt;b&gt;사용자의 상태가 변화할 때마다 지표를 전송하는 것&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 배포 등으로 애플리케이션이 재시작 될때마다 메모리가 초기화되어버리니,&amp;nbsp;&lt;b&gt;매번 현재 지표값을 계산해서 등록&lt;/b&gt;해야했다. 따라서 하루에 한 번씩 특정 상태에 있는 사용자 수를 데이터베이스에서 계산한 다음 지표로 보내는 작업을 &lt;b&gt;Job에 추가&lt;/b&gt;했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 두 서비스 간의 현재 지표값의 싱크를 맞추는 일은 하루에 한번이면 충분하고, 사용자의 상태가 변할 때마다 재계산해서 지표를 보내는 것은 과하다는 판단에 의해서이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gauge를 다루는 것은 까다롭고 복잡했다. 이미 Gauge를 도입한 다른 팀 코드를 참고하면서 여러 번 테스트했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지표 보내는 작업을 Job에 추가한 것이 원인이 되어 다른 문제가 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Job 실행 이후에도 지표가 전송되지 않은 문제&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;Job에 지표를 전송하는 코드를 추가했다. 근데 이상하게 Job이 성공적으로 끝나도 지표는 전송되지 않았다. 원인을 파악하기 위해 프로메테우스 로그를 확인하거나 애플리케이션 로직에 로그를 추가해 문제가 생기는 부분을 파악해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 가설을 세워 접근한 끝에 원인을 찾을 수 있었다. Prometheus에게 지표를 pulling 방식으로 가져온다. 그러나 지표 전송 직후 Job이 종료되기 때문에 지표가 전송되지 않은 것이었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;Prometheus PushGateway&lt;/b&gt;를 도입하였다. Pull 방식이 아닌, HTTP 요청으로 직접 지표 정보를 Push하여 Prometheus와 연결된 PushGateway로 지표를 전송한다. 이 경우 Job이 즉시 종료되어도 데이터는 PushGateway에 남아있기 때문에, Prometheus가 안전하게 지표를 수집해갈 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rHhOK/btsPFhj4G39/zctLlURVSN5qup48VL2M41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rHhOK/btsPFhj4G39/zctLlURVSN5qup48VL2M41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rHhOK/btsPFhj4G39/zctLlURVSN5qup48VL2M41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrHhOK%2FbtsPFhj4G39%2FzctLlURVSN5qup48VL2M41%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;518&quot; height=&quot;355&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;848&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus PushGateway를 사용하는 방법은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1754119973612&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  config:
    activate:
      on-profile:
      	- profile_name
management:
  metrics:
    export:
      prometheus:
        pushgateway:
          enabled: true
          base-url: ${PROMETHEUS_PUSH_GATEWAY_URL:localhost:9091}
          job: job_name
          push-rate: 2s
          shutdown-operation: push&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;이렇게 하면 특정 profile에 속한 job에 대해 PushGateway를 사용해 지표를 전송할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이러한 방식을 사용할 때는 주의가 필요하다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 인스턴스가 사용하는 경우&amp;nbsp; PushGateway가 단일 장애 지점이 될 수 있다.&lt;/li&gt;
&lt;li&gt;오래된 메트릭을 자동으로 지우지 않고 영구적으로 보관한다. 인스턴스의 생명주기와 별도로 동작하기 때문에 예상치 못한 에러를 발견할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;지표를 활용한 알림 생성하기&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 나는 메트릭 시스템을 다뤄본 적이 없어 그라파나를 사용해서 지표를 확인하는 것도 어려웠다. 알림을 생성하기 위해 지표를 확인하고 알림 조건을 작성하는 방법에 대해 알아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그라파나 대시보드로 지표 관찰하기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;647&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFB5Na/btsPFgSlFq6/4imQ5RVm2NVnR0snlVIlJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFB5Na/btsPFgSlFq6/4imQ5RVm2NVnR0snlVIlJK/img.png&quot; data-alt=&quot;출처: Grafana&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFB5Na/btsPFgSlFq6/4imQ5RVm2NVnR0snlVIlJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFB5Na%2FbtsPFgSlFq6%2F4imQ5RVm2NVnR0snlVIlJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;647&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;647&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: Grafana&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;위와 같은 Metric browser를 사용해 원하는 지표를 탐색하고, 적절하게 Label을 설정해서 그래프를 관찰하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;알림 대시보드 확인하기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1504&quot; data-origin-height=&quot;1330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmn0bI/btsPD1uXBls/vn1E8dVmCyfA5BVng8aMG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmn0bI/btsPD1uXBls/vn1E8dVmCyfA5BVng8aMG0/img.png&quot; data-alt=&quot;출처 : Grapana&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmn0bI/btsPD1uXBls/vn1E8dVmCyfA5BVng8aMG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbmn0bI%2FbtsPD1uXBls%2Fvn1E8dVmCyfA5BVng8aMG0%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;788&quot; height=&quot;697&quot; data-origin-width=&quot;1504&quot; data-origin-height=&quot;1330&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : Grapana&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 사진은 알림 설정 예시화면이다. &lt;i&gt;sum by&lt;/i&gt; 등의 쿼리문을 활용해 알림 조건을 설정하는 것을 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지표를 사용해서 두 서비스 간의 지표 값의 불일치 발생 시 알림을 발생시키도록 쿼리를 생성해보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;알림 조건을 위한 쿼리 구현하기&amp;nbsp;&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;프로메테우스의 지표를 사용해 쿼리를 구현하기 위해서는 PromQL (Prometheus Query Language) 을 익혀야한다. PromQL에 대한 자세한 내용은 &lt;a href=&quot;https://prometheus.io/docs/prometheus/latest/querying/basics/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;서 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 서비스의 지표 값과 다른 서비스의 지표 값을 비교해서 차이가 발생했을 때 알림을 트리거하는 조건 예시이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754122636798&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;abs(sum(우리_서비스의_지표_이름) - sum(다른_서비스의_지표_이름)) &amp;gt; 5&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1754123985428&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sum(우리_서비스_지표_이름) &amp;gt; sum(다른_서비스_지표_이름) * 0.3&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;pre id=&quot;code_1754124360161&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(
  abs(
    A - B
  )
  / B
) &amp;gt; 0.5 and B &amp;gt; 10&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;A와 B의 차이가 기준값의 50% 이상인 경우, 그리고 지표 B의 값이 유의미한 양일 때 (10 이상) 알림을 발생한다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  프로젝트 후기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지표를 설정하고, 이를 사용해서 알림을 생성하는 단순해보이는 과제다. 하지만 팀의 코드베이스에서 지표를 전송하는 흐름을 이해하고, 처음 써보는 그라파나 대시보드 사용에 익숙해지고, 지표를 사용해 직접 쿼리도 작성해야 했다. 더욱이 Gauge라는 다소 까다로운 친구를 다루고자 노력하는 과정이 필요했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뿐만 아니라 우리 서비스와 다른 서비스가 어떤 방식으로 상호작용하고 있는 지 이해하였다. 그리고 적절한 값을 지표로 전송하기 위해 데이터베이스에 어떤 쿼리를 날려 데이터를 수집할 지 고민하면서 도메인에 대한 이해도를 키울 수 있었다. 추가로 쿠버네티스 pod이나 job을 다루기 위한 기본적인 명령어를 익힐 수 있었다.&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;아는 것이 거의 0이었던 시점에서 시작해서 이 프로젝트로 인해 도메인 이해, 코드 베이스 이해, 그라파나 사용에 대한 이해, 우리 서비스의 지표 시스템에 대한 이해를 할 수 있었다. 익숙하지 않은 것 투성이라 한 발 내딛을 때마다 막히는 느낌을 받았지만, 끝까지 잘해냈다고 생각해서 뿌듯하다.  &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;b&gt;참고&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.micrometer.io/micrometer/reference/concepts/gauges.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.micrometer.io/micrometer/reference/concepts/gauges.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754084144854&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;Gauges :: Micrometer&quot; data-og-description=&quot;Micrometer supports one last special type of Gauge, called a MultiGauge, to help manage gauging a growing or shrinking list of criteria. This feature lets you select a set of well-bounded but slightly changing set of criteria from something like an SQL que&quot; data-og-host=&quot;docs.micrometer.io&quot; data-og-source-url=&quot;https://docs.micrometer.io/micrometer/reference/concepts/gauges.html&quot; data-og-url=&quot;https://docs.micrometer.io/micrometer/reference/concepts/gauges.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.micrometer.io/micrometer/reference/concepts/gauges.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.micrometer.io/micrometer/reference/concepts/gauges.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;Gauges :: Micrometer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Micrometer supports one last special type of Gauge, called a MultiGauge, to help manage gauging a growing or shrinking list of criteria. This feature lets you select a set of well-bounded but slightly changing set of criteria from something like an SQL que&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.micrometer.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://prometheus.io/docs/practices/pushing/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://prometheus.io/docs/practices/pushing/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754119924896&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;When to use the Pushgateway | Prometheus&quot; data-og-description=&quot;Prometheus project documentation for When to use the Pushgateway&quot; data-og-host=&quot;prometheus.io&quot; data-og-source-url=&quot;https://prometheus.io/docs/practices/pushing/&quot; data-og-url=&quot;https://prometheus.io/docs/practices/pushing/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bDqv0p/hyZviZOS7c/WOIKBsxQitKNJmhbwjeM0k/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200&quot;&gt;&lt;a href=&quot;https://prometheus.io/docs/practices/pushing/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://prometheus.io/docs/practices/pushing/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bDqv0p/hyZviZOS7c/WOIKBsxQitKNJmhbwjeM0k/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200');&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;When to use the Pushgateway | Prometheus&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Prometheus project documentation for When to use the Pushgateway&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;prometheus.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/queries-conditions/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/queries-conditions/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754124220080&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;Queries and conditions |  Grafana documentation&quot; data-og-description=&quot;Grafana Cloud Enterprise Open source Queries and conditions In Grafana, queries fetch and transform data from data sources, which include databases like MySQL or PostgreSQL, time series databases like Prometheus or InfluxDB, and services like Amazon CloudW&quot; data-og-host=&quot;grafana.com&quot; data-og-source-url=&quot;https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/queries-conditions/&quot; data-og-url=&quot;https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/queries-conditions/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pAhFt/hyZqRXjRmp/uIiT9O87wKYMNq52nlRiQ1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cYbEDt/hyZqW5mZzg/UOcgGj59lzoFzHiQj0INB1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/queries-conditions/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/queries-conditions/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pAhFt/hyZqRXjRmp/uIiT9O87wKYMNq52nlRiQ1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cYbEDt/hyZqW5mZzg/UOcgGj59lzoFzHiQj0INB1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Queries and conditions | Grafana documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Grafana Cloud Enterprise Open source Queries and conditions In Grafana, queries fetch and transform data from data sources, which include databases like MySQL or PostgreSQL, time series databases like Prometheus or InfluxDB, and services like Amazon CloudW&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;grafana.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;</description>
      <category>업무/기술회고</category>
      <author>MINGYUM</author>
      <guid isPermaLink="true">https://mingyum119.tistory.com/373</guid>
      <comments>https://mingyum119.tistory.com/373#entry373comment</comments>
      <pubDate>Sat, 2 Aug 2025 17:52:24 +0900</pubDate>
    </item>
    <item>
      <title>[회고] 2025 상반기 회고   모로 가도 서울만 가면 된다!</title>
      <link>https://mingyum119.tistory.com/372</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9RTFp/btsOXRUTjxs/Wc7fpcrk0qJKwsYLkyVJQk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9RTFp/btsOXRUTjxs/Wc7fpcrk0qJKwsYLkyVJQk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9RTFp/btsOXRUTjxs/Wc7fpcrk0qJKwsYLkyVJQk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9RTFp%2FbtsOXRUTjxs%2FWc7fpcrk0qJKwsYLkyVJQk%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;480&quot; height=&quot;363&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;363&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;많은 일이 있었던 2024 하반기 회고를 &lt;b&gt;시원하게 날려먹고&lt;/b&gt; 쓰는&lt;b&gt; 2025 상반기 회고!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한달 회고 작성하는 것도 미루고 미루다 미룬이가 되어버렸다. 그래도 일년에 두번은 써야지 음음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 지금 독일에 있다. 2월 말에 독일로 넘어와 3월부터 &lt;b&gt;백엔드 개발자&lt;/b&gt;로 일을 시작했다.&lt;br /&gt;한국에서 꿈꿨던 외국 생활처럼 모든 게 낭만적이고 여유롭지만은 않다. 오히려 한국에서 독일로 넘어온 과도기적 시기다보니 신경쓸 게 많고 새로운 환경에 적응하느라 정신이 없다.&lt;br /&gt;그래도 어찌저찌 시간이 흘러 입사한 지 4개월이 지났고 어느덧 &lt;b&gt;5개월차&lt;/b&gt;를 바라보고 있다. 모든 게 어색했던 초반과 다르게 생활에도 익숙해지고 여기저기 여행도 많이 다니고, 이제는 일도 많아지는 걸 보니 팀 내에서 정식 팀원 비슷한 게 된 것 같기도 하다. Temporary House도 곧 계약기간이 끝나 7월에 이사를 앞두고 있다. 적응하지 못할 것만 같던 남자친구와의 장거리연애도 어느덧 익숙해졌다. 수습기간도 2개월을 남겨두고 한국에 방문하는 일정도 코앞으로 다가왔다. 하루하루 덜컹거리는 것 같다가도 몇 개월 지나고 돌이켜보면 앞으로 잘 나아가고 있는 듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;완벽주의와의 싸움&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 아직도 어려운 것과 부족한 것이 참 많다고 느껴진다. 익숙하지 않은 언어 (영어) 를 사용해 내가 생각하는 *&amp;lsquo;&lt;b&gt;좋은 태도&lt;/b&gt;&amp;rsquo;를 구현해내기가 힘들다. 좋은 동료가 되고 싶은 마음은 한가득이지만 언어에서 막혀서 회피할 때가 많다. 업무적으로는 잘 소통하려고 노력하고 있다. 필요한 질문을 하고 답변을 하는 것에는 큰 어려움이 없다. 다양한 국적의 사람들과 영어라는 한가지 언어로 소통하며 딱히 문화의 차이로 불편함을 느낀 적은 없지만, 새로운 언어를 사용해 사람들과 허물없이 소통하는 것은 차원이 다른 문제이다. 원래도 친화력이 좋은 편은 아니긴 했다만 한국어로 사람들과 소통해오고 한국 정서에 맞는 말장난으로 웃고 떠들며 친해지던 나는 외국인 앞에만 서면 노잼인간이 된다. 항상 유잼인간이어야 하는 법은 없지만, 표정이 굳고 말투에 자신이 없어지게 되며 말을 줄이게 되는게 문제이다. 누군가에게 용기내서 말을 걸었지만 삐걱거리는 대화로 끝나버리게 되면 또다시 자신감을 잃게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 아직도 팀원들과 친해지지 못하고 겉돌고 있다고 느낀다. 아주 조금씩 더 노력해서 말문을 계속 트는 수밖에 없다. 완벽주의 성향을 가지고 있다고 생각해본적은 별로 없지만, 이럴 때마다 완벽주의의 벽에 부딪히는 느낌을 받는다. 내가 하고싶은 말을 완벽하게 전달할 수 없을 것 같다면 애초에 대화를 시작하지 않는 순간이 있다. 이게 지금의 나에게 &amp;lsquo;&lt;b&gt;컴포트 존&lt;/b&gt;&amp;lsquo;이다. 누가 나를 어떻게 생각하든 일단 도전해보고 실패하는 과정이 필요하다. 부족한 영어를 들킬때마다 부끄럽고 숨고 싶지만 그런 과정이 없으면 평생 부족한 상태로 살아야한다. 하반기에는 조금 더 팀에 녹아들고 업무적으로도, 개인적으로도 원활히 소통하는 내가 되었으면 좋겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;새로운 취미들의 발견&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 환경에 와서 가장 마음에 드는 변화이다. 나는 뛰어노는 걸 좋아한다. 앉아서 수다떠는 것도 좋지만 단체 스포츠를 하며 사람들과 어울리는 것을 좋아한다. 여기 와서는 그 환경이 잘 조성되어있다는 걸 느낀다. 배구, 배드민턴, 테니스, 축구, 클라이밍, 러닝 등 시간만 많으면 할 수 있는 운동이 많다. 특히 배드민턴과 클라이밍을 좋아해서 여러번 시도하고 있다. 회사 근처 가까운 곳에 배드민턴 장이 있었음 좋겠다.&lt;br /&gt;그리고 요리도 시작했다. 귀찮아서 밥에 닭가슴살 데워먹던 내가 요리를 한다! 처음에는 손에 익지 않아 한번 요리하면 한시간 씩 걸렸는데, 요즘은 아는 요리하면 레시피도 안보고 뚝딱할 수 있을 때도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_5120.jpg&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NHpvd/btsOYTdot7X/BN1IyFKpecwKEakDJzvIK1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NHpvd/btsOYTdot7X/BN1IyFKpecwKEakDJzvIK1/img.jpg&quot; data-alt=&quot;요리 인스타그램도 만듦. 많관부&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NHpvd/btsOYTdot7X/BN1IyFKpecwKEakDJzvIK1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNHpvd%2FbtsOYTdot7X%2FBN1IyFKpecwKEakDJzvIK1%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;200&quot; height=&quot;368&quot; data-filename=&quot;IMG_5120.jpg&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;2365&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;요리 인스타그램도 만듦. 많관부&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;돈을 벌기 시작해서 경제 공부, 주식 공부도 하고 있다. 취업하면 재테크 해보고 싶은 로망(?) 이 있었다. 어른의 세계랄까 .. 사회, 경제, 정치에 무지하고 꽃밭에 살던 내가 세상을 하나씩 알아간다는 게 재미있는 것 같다. 경제 책도 많이 읽었다! 잘 모르는 분야의 기본적인 것부터 차근차근 배워나가는 재미가 있다. 하지만 역시 꾸준히 무언가를 관리한다는 건 .. 어렵다 .. 나도 언젠간 재테크 고수가 되겠지 (도와줘요 구구) 부끄럽지만 아침에 신문도 읽는다 ㅎㅎㅎ 생각보다 재밌어서 스터디 만들고싶은거 참는 중&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M3rJ6/btsOZTDjb0j/tfTshGAquUhjuknHzOazoK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M3rJ6/btsOZTDjb0j/tfTshGAquUhjuknHzOazoK/img.jpg&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;1200&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.7275%; margin-right: 10px;&quot; data-widthpercent=&quot;33.51&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M3rJ6/btsOZTDjb0j/tfTshGAquUhjuknHzOazoK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM3rJ6%2FbtsOZTDjb0j%2FtfTshGAquUhjuknHzOazoK%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;810&quot; height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm3qZf/btsOWMsM46R/72LWOnlXmFZoE4Zk4URBWk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm3qZf/btsOWMsM46R/72LWOnlXmFZoE4Zk4URBWk/img.jpg&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;679&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.7043%; margin-right: 10px;&quot; data-widthpercent=&quot;33.48&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm3qZf/btsOWMsM46R/72LWOnlXmFZoE4Zk4URBWk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm3qZf%2FbtsOWMsM46R%2F72LWOnlXmFZoE4Zk4URBWk%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;458&quot; height=&quot;679&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yDVQM/btsOXRUUk8a/OeW3neArkSexB0rHVylbvk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yDVQM/btsOXRUUk8a/OeW3neArkSexB0rHVylbvk/img.jpg&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;1200&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.2426%;&quot; data-widthpercent=&quot;33.01&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yDVQM/btsOXRUUk8a/OeW3neArkSexB0rHVylbvk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyDVQM%2FbtsOXRUUk8a%2FOeW3neArkSexB0rHVylbvk%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;798&quot; height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;/div&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;완독한 3권의 책. 단연 최고의 책은 &amp;lt;왜 그들만 부자가 되는가&amp;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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 개발 블로그니까 개발 이야기를 해야겠지? 처음으로 회사의 일원으로서 업무를 시작했다. 한 달 남짓의 온보딩 과정과 첫 티켓을 해치운 뒤 &lt;b&gt;캡스톤 프로젝트&lt;/b&gt;에 들어갔다. 캡스톤 프로젝트는 약 2개월 간 신입 개발자에게 주어지는 업무이고, 프로젝트 이후 발표도 해야했다. 6월 초중순 나 포함 3월 입사자들의 캡스톤 프로젝트 발표가 무사히 끝났다. (&lt;b&gt;Congrats!&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;현업은 이상과 달랐다&lt;/b&gt;. 우테코 미션과 팀플 때는 항상 요구사항이 명확했고 이 티켓에서 무엇을 해야하는지, 어떤 코드를 수정해야할지 미리 그림을 그릴 수 있었다. 하지만 신입에게 팀 코드는 블랙박스와 같았고, 내가 아닌 다른 사람이 작성한 요구사항을 이해하는 것부터 난제였다. 그리고 놀라웠던 점은 &lt;b&gt;처음부터 모든 요구사항이 정의되지 않았다.&lt;/b&gt; 우테코 때는 직접 요구사항을 작성하기 때문에 &amp;lsquo;어떻게?&amp;rsquo;를 고민하며 작성한다. 당연하지만 여기선 구현을 염두에 두고 티켓을 작성하지 않기 때문에 나 스스로 &amp;lsquo;어떻게&amp;lsquo;를 주어진 목표에 맞춰 고민해야한다. 그리고 구현 과정에서 토론하며 요구사항이 바뀌는 경우가 종종 있다. 그래서 구현, 피드백, 수정 단계가 여러번 반복되었고 그럴 때마다 답답함을 느끼기도 하였다. 하지만 현업이 항상 이상적으로 흘러가지 않는다는 걸 이해하면서 &lt;b&gt;&amp;rsquo;최대한 빠르게 구현하기&amp;lsquo;&lt;/b&gt;가 중요한 이유를 한번 더 깨닫게 되었다. &amp;ldquo;돌아가는 쓰레기를 만들라&amp;rdquo;라는 포비의 말씀이 떠올랐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;375&quot; data-origin-height=&quot;219&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2gLZz/btsOWKPkndO/H6fmEXLsPmOGsVZiGQkQW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2gLZz/btsOWKPkndO/H6fmEXLsPmOGsVZiGQkQW1/img.png&quot; data-alt=&quot;아 우테코 그리워요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2gLZz/btsOWKPkndO/H6fmEXLsPmOGsVZiGQkQW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2gLZz%2FbtsOWKPkndO%2FH6fmEXLsPmOGsVZiGQkQW1%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;375&quot; height=&quot;219&quot; data-origin-width=&quot;375&quot; data-origin-height=&quot;219&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;여기서도 완벽주의에 대한 이야기가 나오는데, 처음부터 완벽하게 하려할수록 피드백 반영 시기가 늦어지고 결국에 주어진 시간에 나올 수 있는 퀄리티가 낮아지기 마련이라는 걸 느꼈다. 텍스트로 주어진 요구사항을 혼자 파악하려고 하지말고, 많이 물어보고, 최대한 빨리 구현하고, 피드백하는 것이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;태스크 하나를 마무리한 시점에 기술 회고를 작성해보는 것도 좋을 것 같다. 새로 배운것들을 정리하고 '왜'라는 질문에 스스로 답해보며 개발 과정을 돌아볼 수 있을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일과 삶의 균형&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전반적으로 봤을 때, 일적으로도 삶적으로도 챙길게 많고 욕심나는 것이 많아서 어영부영 보낸 상반기였다. 무언가에 빡세게 하나에만 매진하며 지내온 예전과 다르게, 여러가지를 한번에 챙겨서 그런지 지나간 시간들이 조금 흐리멍텅하게 느껴지기도 한다. 그렇게 느낀 이유를 돌이켜 보면 &amp;lsquo;일과 삶의 균형&amp;rsquo;이라는 게 나에게 익숙한 문화가 아니여서 그런가 싶기도 하다.&lt;br /&gt;우테코 때는 코딩만 했었다. 쉬는 날에도 코딩을 했다보니 모든 시간을 &amp;rsquo;일&amp;lsquo;로 채우는 것이 나에게는 익숙한 문화였다. 퇴근하고 일하지 않는 것이 어색해서 우테코 수료 후 입사한 인턴 때도 관성처럼 야근을 했었다. 물론 처음하는 업무라 일을 배우고싶다는 의지 때문이기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하지만 지금은 정해진 시간만큼 일하고 집에 간다&lt;/b&gt;. (입사 초기에 해 쨍쨍 떠있는데 퇴근하는 거 기분 되게 묘했음) 그리고 아직도 퇴근 후 생산적인 활동을 하지 않는게 어렵다. 그래서 운동이든 재테크든 이것저것 퇴근 후의 시간에 넣는다. 최근에 본 &amp;lsquo;&lt;a href=&quot;https://www.youtube.com/watch?v=AqEN8qOcAcA&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;조승연의 탐구생활&lt;/a&gt;&amp;lsquo; 유튜브를 보고 이에 대해 생각하게 되었다. 생산적이지 않은 일에 시간을 쓰는 것이 아깝다. 이런 사고방식을 고집하며 살다보니 삶이 삭막하다고 느껴질 때도 있다. 몸만 외국에 왔지 생각하는 것은 아직도 여실히 한국인같다는 것을 느끼는 요즘이다. 무용한 것으로 시간을 보내며 여유를 가지자는 다짐을 해볼 수 있지만 아직까지는 생산적인 시간으로 채우는 것이 좋다. 이건 잘 모르겠으니 그냥 흘러가는 대로 친구들하고 맛있는 거 먹고 놀고 하고싶은 공부도 해야겠다. 헤헷&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;감정과 생활을 분리하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(여기서부터는 TMI) 감정을 일상생활로 끌어들이지 않는 연습을 하고 있다. 감정은 오르락내리락 반복하면서 날 힘들거나 즐겁게 한다. 이 반복을 좀 겪다보니, 그리고 여유가 생겨서 관찰할 수 있게 되다보니, 아무리 힘들고 슬픈 날이 있다 하더라도 결국에는 다시 괜찮은 날이 올거라는 걸 알게 되었다. 원래는 무언가 감정에 휩쓸려 평소에 하던 루틴을 포기한다거나, 부정적인 감정에 현재 처한 상황마저 부정적으로 인식했다. &lt;b&gt;하지만 상황은 생각보다 부정적이지 않았다&lt;/b&gt;. 그냥 감정이 이성 대신 상황에 대한 판단을 했던 것이다. 그래서 최근에는 이를 의식적으로 분리하려 하고 있다. 아무리 기분이 별로여도, 하룻밤만 자고 나면 다시 기분이 나아질 걸 알고있으니 그 기분에 잠식되지 않을 수 있다. 좋지 않은 기분을 그냥 있는 그대로 두고 하던거 하다보면 어느샌가 또 괜찮아져있다.&lt;br /&gt;반대로 상황을 감정적으로 인식하지 않게 되기도 한다. &lt;b&gt;독일에 살다보면 내 마음대로 상황이 흘러가지 않는다&lt;/b&gt;. 무언가 상황을 통제하려고 하는 건 소용이 없다. 다들 천천히 느긋하게 흘러가기 때문이다. 그러한 상황을 몇 번 겪다보니 불안한 상황에 마주하게 되더라도 마음이 느긋해지는 것 같다. 최악을 상상하고 최악의 상황이 되더라도 죽지는 않는다고 생각하니 뜻대로 되지 않는 상황에 별 생각이 없어진달까?&lt;br /&gt;무언가 선택할 때도 항상 최고의 선택을 해야하고, 효율적이어야 하고, 일희일비하고, 감정에 휩쓸려 쉽게 무너지고, 뜻대로 되지 않으면 화가 나던 나로서는 신선한 감각이다. 무언가 잘 안풀려도 결국엔 모든게 제자리로 돌아갈 것이라고 생각하니 마음이 편하다. 그냥 나는 하던대로 맛있는 거 먹고 운동하고 일하고 하고싶은 거하면서 지내면 된다. 앞으로도 이를 자각하면서 생활 속에 좀 더 녹아들었으면 하는 바람이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*좋은 태도: 많이 질문하고, 나의 상황을 많이 공유하면서 피드백을 받는 것에 열려있는 태도&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2O6BN/btsOXU5bqES/qStSQEupwx8ZgRxUyuzIz1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2O6BN/btsOXU5bqES/qStSQEupwx8ZgRxUyuzIz1/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;IMG_3834.JPG&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2O6BN/btsOXU5bqES/qStSQEupwx8ZgRxUyuzIz1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2O6BN%2FbtsOXU5bqES%2FqStSQEupwx8ZgRxUyuzIz1%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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/etg6pi/btsOXfIhmkl/NKzWNkkbGuJ5wIdOlbeyo0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/etg6pi/btsOXfIhmkl/NKzWNkkbGuJ5wIdOlbeyo0/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;IMG_3939.JPG&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/etg6pi/btsOXfIhmkl/NKzWNkkbGuJ5wIdOlbeyo0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fetg6pi%2FbtsOXfIhmkl%2FNKzWNkkbGuJ5wIdOlbeyo0%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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mHrpq/btsOXUqCfx5/PgUvtytuWtFFkiZHtk8wk0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mHrpq/btsOXUqCfx5/PgUvtytuWtFFkiZHtk8wk0/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;IMG_6070.JPG&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mHrpq/btsOXUqCfx5/PgUvtytuWtFFkiZHtk8wk0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmHrpq%2FbtsOXUqCfx5%2FPgUvtytuWtFFkiZHtk8wk0%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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pp0JR/btsOYOXiHOB/TBJM5aajXtkkr3Eo1V6qK0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pp0JR/btsOYOXiHOB/TBJM5aajXtkkr3Eo1V6qK0/img.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; data-is-animation=&quot;false&quot; data-filename=&quot;IMG_6380.JPG&quot; style=&quot;width: 63.2558%; margin-right: 10px;&quot; data-widthpercent=&quot;64&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pp0JR/btsOYOXiHOB/TBJM5aajXtkkr3Eo1V6qK0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPp0JR%2FbtsOYOXiHOB%2FTBJM5aajXtkkr3Eo1V6qK0%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;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbVYgB/btsOYx9lNiu/IjQIYOULkFBLumQtUTZSk1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbVYgB/btsOYx9lNiu/IjQIYOULkFBLumQtUTZSk1/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-filename=&quot;IMG_8554.JPG&quot; style=&quot;width: 35.5814%;&quot; data-widthpercent=&quot;36&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbVYgB/btsOYx9lNiu/IjQIYOULkFBLumQtUTZSk1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbVYgB%2FbtsOYx9lNiu%2FIjQIYOULkFBLumQtUTZSk1%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;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;암튼 여기 와서의 생활이 재밌고 좋다. 한국이 그리워질 때가 정말 많지만 그럼에도 불구하고 만족스러운 하루를 보내고 있다. 힘껏 당겨진 고무줄처럼 팽팽한 삶을 살다가 여유가 생긴 삶을 사는 것도 나를 한층 업그레이드 시켜준다.&amp;nbsp; 뚜렷한 목표 없이 그냥저냥 하고싶은대로 살아도 나름 이런저런 생각도 하면서 꽤나 충만하게 채워질 수 있다는 걸 알았다. 모로 가도 서울만 가면 된다는데, 느슨하게 살아도 성장했음을 느꼈으니 된거 아니겠나? 목표가 있을 때만 하루하루 열심히산다는 거에 만족하면서 살 수 있는 사람이라고 생각했는데 꼭 그렇지만은 않나보다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 하반기에는 뚜렷한 목표를 성취하고 싶다. 업무적으로도, 일상적으로도 뜻깊은 과정과 가시적인 결과가 있는 하반기를 보냈으면 좋겠다.&lt;/p&gt;</description>
      <category>Other/기록</category>
      <author>MINGYUM</author>
      <guid isPermaLink="true">https://mingyum119.tistory.com/372</guid>
      <comments>https://mingyum119.tistory.com/372#entry372comment</comments>
      <pubDate>Tue, 1 Jul 2025 09:51:58 +0900</pubDate>
    </item>
  </channel>
</rss>