<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Carry On Progamming</title>
    <link>https://zorba91.tistory.com/</link>
    <description>초급 개발자의 기록</description>
    <language>ko</language>
    <pubDate>Sun, 17 May 2026 23:26:32 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>희랍인 조르바</managingEditor>
    <image>
      <title>Carry On Progamming</title>
      <url>https://tistory1.daumcdn.net/tistory/2817183/attach/6e44e7a5cba64607b2ac9938dd1853c2</url>
      <link>https://zorba91.tistory.com</link>
    </image>
    <item>
      <title>자율주행 관제로 정신없었던 2025년 회고</title>
      <link>https://zorba91.tistory.com/376</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;블로그를 계속 쓰는게 맞을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT처럼 LLM 서비스가 발전해서 블로그는 멸종하지 않을까란 생각이 든다.&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2024년 회고를 건너뛰었다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2024년 회고 글을 따로 작성하지 않았다. 2024년에도 쓸 이야기가 없었던건 아니지만 고도화가 어느 정도 완료된 서비스, 회사에서 관심도가 떨어지고 요구사항이 없는 서비스가 되어 스스로 일을 만들어서 했다. 그렇다해도 자동차검사 예약 담당자들(기획, 디자인, 프론트)과 으쌰으쌰해서 차량 데이터 확보가 목적이었던 서비스를 기대보다 훨씬 높은 매출을 만들어내는 서비스로 만들었다. (런칭 당시 일일 예약 100건이 안되었지만, 마지막 확인했을땐, 일 평균 예약이 700건이었다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 여유가 될때마다 테스트 커버리지를 높이는 일에 집중했었다. 테스트 커버리지 100%라는 &lt;a href=&quot;https://www.youtube.com/watch?v=jdlBu2vFv58&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;영상&lt;/span&gt;&lt;/a&gt;을 보기도 했어서 개발자에게 어느정도의 안정감을 주는지 궁금하기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동차검사 예약 서비스의 테스트 커버리지를 100%로 만들진 못하고, 70% 정도까지 높였다. 스스로 커버리지를 지키도록 유닛 테스트만 실행할 땐 60%, 통합 테스트까지 돌릴땐 70% 커버리지가 안되면 빌드를 실패하게 만들었다. 코드가 추가될때마다 사이드 이펙트와 버그를 빠르게 검증할 수 있었고 마음에 안정감을 주는건 확실했다.&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;TDD에 익숙하지 않고 못한다는 증명일 것이다. TDD를 제대로 수행하는 개발자는 테스트 코드를 작성한다고해서 업무 속도가 느려지지 않는다고 하니까.&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025년 시작전부터 쌩뚱맞은 업무를 맞게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동차검사 예약 서비스를 2년 가까운 시간을 유지보수하다보니 서비스에 대한 사내의 관심도가 떨어지고 새로 기능을 추가하기도 제한적인 상황이라 권태로움이 큰 상황이었다. 지나서 쓰는 이야기지만, 2024년 하반기 즈음, 2025년 상반기에는 이직을 해야겠다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 와중에 2024년 11월 말이였던지 12월 초였던지 재택근무를 하고 있었는데, 당시 조직장님(직책이 높으신..)에게서 전화가 왔다. 전화올 일이 없는 분이라 '장애가 났나?' 란 생각으로 얼른 전화를 받았었다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;자율주행차량.jpeg&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;675&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9Q0JA/dJMcagEfPkE/HoBtCaxgNWv2K2TKJbxJXK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9Q0JA/dJMcagEfPkE/HoBtCaxgNWv2K2TKJbxJXK/img.jpg&quot; data-alt=&quot;실제로 작업하는 차다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9Q0JA/dJMcagEfPkE/HoBtCaxgNWv2K2TKJbxJXK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9Q0JA%2FdJMcagEfPkE%2FHoBtCaxgNWv2K2TKJbxJXK%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;1200&quot; height=&quot;675&quot; data-filename=&quot;자율주행차량.jpeg&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;675&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;2024년 연말에는 서포트 형식으로 프로젝트를 돕다가 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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;밀도 높았던 2025년&lt;/h3&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;그만해.jpeg&quot; data-origin-width=&quot;279&quot; data-origin-height=&quot;181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8IB94/dJMcaaRyieN/6EjdeOySc3SKg4l3aB9t7K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8IB94/dJMcaaRyieN/6EjdeOySc3SKg4l3aB9t7K/img.jpg&quot; data-alt=&quot;그..그만해&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8IB94/dJMcaaRyieN/6EjdeOySc3SKg4l3aB9t7K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8IB94%2FdJMcaaRyieN%2F6EjdeOySc3SKg4l3aB9t7K%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;279&quot; height=&quot;181&quot; data-filename=&quot;그만해.jpeg&quot; data-origin-width=&quot;279&quot; data-origin-height=&quot;181&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;b&gt;소프트웨어&lt;/b&gt;: ROS2, MQTT, WebSocket(STOMP), Grpc, Python, Golang, Java, C++(사용하진 않고 읽어야했지만 올해는 쓰는 일도 생길 것 같다), Jetson Ubuntu(Nvidia에서 개발한 ubuntu)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하드웨어&lt;/b&gt;: 자율주행차량에 탑재된 PC(Nvidia Orin PC), RSE(Rear Seat Entertainment), L2 스위치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커리어 내내 클라우드 서비스에서 제공해주는 여러 서비스(서버, DB 등), Java, Kotlin, Spring으로만 개발한 내게 해결해야할 문제들은 새롭고 어려웠다. AI가 없었다면 일정 내에 완수하지 못했을거라 확신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자율주행차량에 설치된 서버 작업을 하는게 큰 고역이었다. 처음 자율주행차량을 봤을 땐 설렜지만, 클라우드 서비스로 딸깍하면 되던 것들을 직접 처리해야했다. 사무실 pc에선 잘되었지만, 차량 pc에선 안될 때가 많아서 좁은 차 안에서 몇시간씩 박혀서 디버깅을 하는 경우도 잦았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;208122081000.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbxjCV/dJMcagEfPp9/ZJcGIAFLWYEeLHEGwGY8F1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbxjCV/dJMcagEfPp9/ZJcGIAFLWYEeLHEGwGY8F1/img.jpg&quot; data-alt=&quot;차 안에서의 작업&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbxjCV/dJMcagEfPp9/ZJcGIAFLWYEeLHEGwGY8F1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbxjCV%2FdJMcagEfPp9%2FZJcGIAFLWYEeLHEGwGY8F1%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;500&quot; height=&quot;554&quot; data-filename=&quot;208122081000.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;차 안에서의 작업&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;os 설치부터 이더넷 포트들 각각 목적에 맞게 고정 ip 설정, 네트워크 통신 테스트, 여러 필요한 스크립트 작성(배포 파이프라인, systemd service, 리눅스 패키지 설치, 초기 세팅) 등이었다. 덕분에 OS, 네트워크, 하드웨어를 부딪혀보며 배우는 기회였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PC에 이더넷 포트에 랜선을 꽂아서 RSE와 통신이 잘되는지, 자율주행 메인 pc와 통신이 잘되는지 체크했다. 서버 개발자면서 초짜 인프라 엔지니어처럼 일했다.&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇가지 에피소드가 있었다. 차량의 pc만 껐다켜면 배포 스크립트를 통해 배포가 잘되는데, 시동을 껐다켜면 배포가 되지 않는 이슈가 있었다. 이 이슈를 구분해내는데도 시간이 걸렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 찾은 원인은 시동을 껐다켜면 LTE 라우터보다 PC가 먼저 켜질 경우, 배포 스크립트 중에 인터넷을 통해 체크하는 로직이 실패해서 배포가 안되는 것이었다. PC만 껐다켜면 배포가 잘 되는 이유는 이미 LTE 라우터는 켜져있기 때문이었다. 이건 백엔드 개발자가 상상하기 어려운 원인이라 '와.. 이걸 찾은 것도 다행이다'란 생각을 했다. (로그도 인터넷이 안된게 아니라 권한 인증 실패라 뜨니 짐작하기 어려웠다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Orin PC와 RSE 연결 과정 중에 pci express로 확장된 이더넷 포트로 L2 레이어는 인식하지만 실제 통신이 이뤄지지 않았다. 혹시나해서&amp;nbsp; 메인 이더넷 포트로 테스트 해봤더니 통신이 되는 경우도 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Orin PC의 와이파이가 작동하지 않아서 온갖 삽질을 다해가면서 소프트웨어 레벨에서 원인을 분석했는데, 와이파이 모듈의 전선이 끊겨있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-size: 1.44em; letter-spacing: -1px; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;빠르게 2025년 업무 훑기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연말 셀프 리뷰를 작성하면서 한해 수행했던 일들을 몇가지 기록으로 남겨본다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;시스템 안정성 확보
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;개발환경, 운영환경 클러스터 분리 및 EOS된 클러스터 마이그레이션&lt;/li&gt;
&lt;li&gt;인프라 마이그레이션(클러스터, DB 등)&lt;/li&gt;
&lt;li&gt;Sentry, Newrelic 적용(Observability 확보)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;차량 Edge 서버(자율주행 차량 내 서버) 구축 및 CI/CD 파이프라인 적용&lt;/li&gt;
&lt;li&gt;관제 시스템 개발(주로 실시간 모니터링을 위한 소켓 서버)&lt;/li&gt;
&lt;li&gt;주행 데이터 시뮬레이터 개발(테스트 때마다 매번 실제 주행을 할 수 없어서 만듦)&lt;/li&gt;
&lt;li&gt;이전 팀 리소스 부족으로 하반기 시작까지 자동차검사 개발 겸직&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자율주행차량 내에 세팅해야할 하드웨어와 소프트웨어 개발은 대부분 혼자해야했다. 왜냐하면 팀에서 해야할 업무들이 그외에도 많았기 때문에 각자 맡은 업무들이 있었다. 온실 속에 자란 개발자인(?) 내게 모르는 기술이 많고 지식이 모자라 AI로도 해결되지 않을때면 머리를 쥐어짜야하는 하루가 잦았다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다고 돈 받는 직장인이 '나 못하겠어요 모르겠어요' 할 수 없으니 기대에 부응하기 위해 1년 동안 야근을 많이했다.&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025년 처음으로 관제 시스템 구축을 시작했기 때문에 아직 부족한 부분이 많고 갈 길이 멀다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;올해 상반기는 실시간 영상 스트리밍, 영상 비식별화 처리, 원격제어&lt;/b&gt;가 큰 미션인데, 잘 해낼 수 있을까 걱정이 들지만 열심히 해보는 수밖에.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상치 못한 도메인 전환이라 고됐지만, 만족스러운 한해였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AI 물결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 물결 속에 허우적 거렸다. 전사적으로 AI 활용을 적극 권장했기 때문에 모른척하고 내 일만 하기 어려웠다. 틈틈이 LLM 원리 공부, 뉴스를 스크래핑해서 슬랙으로 보내주는 MCP 서버 구축, RAG 활용 등을 해보았다. 코파일럿의 instruction.md를 작성해서 답변의 질, 코드 퀄리티를 높이고 커밋 메시지도 팀 컨벤션에 맞게 생성하도록 활용했다. 팀 내 필요한 github app이나 임시용 배치 같은 것들을 코파일럿으로 뚝딱 만들기도 했다.&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>개발 일기장/직장 생활</category>
      <category>2025년 회고</category>
      <category>개발자 회고</category>
      <category>백엔드 회고</category>
      <author>희랍인 조르바</author>
      <guid isPermaLink="true">https://zorba91.tistory.com/376</guid>
      <comments>https://zorba91.tistory.com/376#entry376comment</comments>
      <pubDate>Tue, 13 Jan 2026 01:24:38 +0900</pubDate>
    </item>
    <item>
      <title>2024 if(kakaoAI) Day 3 참석 후기 - 백엔드 위주</title>
      <link>https://zorba91.tistory.com/375</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;기억과 필기에 의존해서 작성한 거라 발표자가 전달한 정보와 다른 부분이 있을 수 있음!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기업에서 개최하는 컨퍼런스를 처음 가보았다.&lt;/p&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;올해의 핵심 키워드는 역시 AI였다. (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;&lt;span style=&quot;color: #333333;&quot;&gt;백엔드 개발자가 들을만한 주제는 Day 3에 몰려있어 Day 3를 신청했는데 당첨되어 ifKakao에 다녀왔다. 글을 쓰려고보니 올해는 ifKakao가 아니라 if(kakao&lt;b&gt;AI&lt;/b&gt;)인걸 알아차렸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;장소는 용인시 고기리에 위치해있는 AI 캠퍼스라고 하는데, 접근성이 떨어져서 카카오측에서 셔틀을 제공해주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;요즘은 기업 테크 컨퍼런스는 코엑스에서 많이하는 추세인거 같은데 역세권과 멀리 떨어진 장소라 의외였다. 좋게 얘기하면 뚝심이 있달까... 판교로 출퇴근하는 나는 그다지 불편하지 않았지만, 외부인들 입장에서는 매우 불편했을지도 모르겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그래도 셔틀은 특정 시간대에 20분 간격으로 운행해서 셔틀을 타는데 불편함은 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-10-24-23-55-56 006.jpeg&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;2992&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9PPtq/btsKjM9QW1a/poRaiW0IptMmb1L1mmokdk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9PPtq/btsKjM9QW1a/poRaiW0IptMmb1L1mmokdk/img.jpg&quot; data-alt=&quot;건물이 아주 이뻤다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9PPtq/btsKjM9QW1a/poRaiW0IptMmb1L1mmokdk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9PPtq%2FbtsKjM9QW1a%2FpoRaiW0IptMmb1L1mmokdk%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;550&quot; height=&quot;550&quot; data-filename=&quot;KakaoTalk_Photo_2024-10-24-23-55-56 006.jpeg&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;2992&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;컨퍼런스를 지원해주시는 직원분들이 많으셨다. 직원분들 모두 활기차게 맞이해주셔서 나도 기분이 좋았다. 입구에 간식을 나눠주고 계셨다. 간식은 견과류와 편의점 커피였고, 물은 얼마든지 가져갈 수 있게 비치되어있었다. 소정의 기념품도 있었는데, if(kakaoAI)가 그려진 홀로그램 스티커와 신발 가방(?) 을 받았다. &lt;s&gt;(기념품은 솔직히 쏘쏘..)&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-10-24-23-55-56 005.jpeg&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;2992&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GJN8N/btsKixe1ou5/xhBcm11rCyNkDUCJNub3S0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GJN8N/btsKixe1ou5/xhBcm11rCyNkDUCJNub3S0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GJN8N/btsKixe1ou5/xhBcm11rCyNkDUCJNub3S0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGJN8N%2FbtsKixe1ou5%2FxhBcm11rCyNkDUCJNub3S0%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;2992&quot; height=&quot;2992&quot; data-filename=&quot;KakaoTalk_Photo_2024-10-24-23-55-56 005.jpeg&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;2992&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위에 찍은 건물 아래가 폴딩 도어로 되어 있어 오픈 테라스로 만들 수 있었다. 큰 스크린의 맞은 편(내 뒷편)은 뚫려있다. 컨퍼런스 시작까지는 폴딩 도어가 열려있었는데 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;바깥 찬 바람이 계속 들어와 &lt;/span&gt;&lt;/span&gt;추웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오 CTO의 기조 연설로 Day 3가 시작했다. 카카오 계열사(카카오 엔터프라이즈, 카카오뱅크, 카카오페이, 카카오엔터테이먼트, 카카오 헬스케어) CTO들이 차례로 나와 각 회사에서 AI를 활용해 어떤 서비스를 하고 있는지 설명했다. 카카오 모빌리티는 CTO의 부재(&lt;s&gt;최근 퇴사를 하셨다&lt;/s&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;AI 용어인지 그 도메인의 용어인지 몰라서 일단 필기하고 나중에 찾아보려고 들리는대로 써놨다 ㅎ&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;카카오 자회사의 AI 적용한 사례 세션 관련&lt;/h3&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;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;- FDS, ADS와 Adaptive ML&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사기 거래 방지를 위해 룰과 ML을 서로 배타적으로 병렬 실행해 두개의 결과를 비교한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MoE 아키텍쳐를 적용함 -&amp;gt; 찾아봤던 링크: &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;a href=&quot;https://developer.nvidia.com/ko-kr/blog/applying-mixture-of-experts-in-llm-architectures/&quot;&gt;https://developer.nvidia.com/ko-kr/blog/applying-mixture-of-experts-in-llm-architectures/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오뱅크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- GenAI(생성형 AI) 가드레일: &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;AI를 보호하는 장치, 부적절한 콘텐츠가 LLM으로 도달하지 않도록 차단한다. 기술보단 정책이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;- 프롬프트 인젝션 방지&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;- XAI(&lt;a href=&quot;https://www.ibm.com/kr-ko/topics/explainable-ai&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.ibm.com/kr-ko/topics/explainable-ai&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오 모빌리티&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자율 주행을 테스트를 계속하고 있다. 혹시나 모를 사고에 대비해 항상 safety driver가 동승한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 웨이모, 우버의 기술력에 미칠 수 있다고 생각하냐는 질문에 경쟁보단 공생이 목표라한다. 웨이모나 우버가 국내 또는 아시아에 진입할 때 그들보다 잘 가공된 데이터를 제공하는 방식으로?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오 헬스케어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'파스타' -&amp;gt; 건강관리 서비스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디바이스로 혈당을 측정해주고, 챗봇으로 유저가 능동적으로 건강관리를 할 수 있도록 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 건강관리 기록(먹은 음식, 운동양 같은?)을 타이핑하기 귀찮으니 음성으로 기록 할 수 있는 피처를 넣을 예정이다였는지 이미 존재한다고 했는지... 기억이 안난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&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;AI를 잘 모르는 나에게 흥미로웠던건 카카오엔터테이먼트에서 사용하는 Helix shorts였다. 웹툰, 웹소설을 AI가 읽어서 유튜브 쇼츠 같은 콘텐츠를 만들어내는 것이다. Vision AI(AI가 이미지를 인식하고 해석하는 기술)를 통해 캐릭터의 감정과 특징을 캐치해내서 요약하고 적절한 배경음악을 선택한단다. '이야 AI가 어떻게 웹툰에 나오는 캐릭터의 감정까지 캐치하지?' 란 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘텐츠 하나 만드는데 시간은 3시간, 비용은 5만원이라고 한다. 모든 쇼츠 콘텐츠를 AI가 만드는 것은 아니고 트래픽의 5%만 A/B 테스트를 하는데 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;버추얼 아이돌도 만들어보려 하는데 단순히 노래만 부르는게 아니라 팬들과 소통할 수 있어야 의미가 있는데 현재 기술로는 어렵다고 한다. 하지만 Helix shorts도 작년에는 현재 기술로는 불가능하다고 했다는데 지금 만든걸 보니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 기술 발전이 빨라 빠른 시일 내에 가능할지도?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-10-24-23-55-55 004.jpeg&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;2992&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rwg9L/btsKixMS7R3/ztKnkI4ypPcu25kHQk65qK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rwg9L/btsKixMS7R3/ztKnkI4ypPcu25kHQk65qK/img.jpg&quot; data-alt=&quot;점심 양은 작지만 맛있었다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rwg9L/btsKixMS7R3/ztKnkI4ypPcu25kHQk65qK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRwg9L%2FbtsKixMS7R3%2FztKnkI4ypPcu25kHQk65qK%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;550&quot; height=&quot;550&quot; data-filename=&quot;KakaoTalk_Photo_2024-10-24-23-55-55 004.jpeg&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;2992&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-10-24-23-55-55 003.jpeg&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;2992&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bREp7v/btsKjmX7HhP/5MoMBtnT8Lr1kvTqUfyGd0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bREp7v/btsKjmX7HhP/5MoMBtnT8Lr1kvTqUfyGd0/img.jpg&quot; data-alt=&quot;깡시골이라 점심 먹고 가볍게 호수 근처 산책&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bREp7v/btsKjmX7HhP/5MoMBtnT8Lr1kvTqUfyGd0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbREp7v%2FbtsKjmX7HhP%2F5MoMBtnT8Lr1kvTqUfyGd0%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;550&quot; height=&quot;550&quot; data-filename=&quot;KakaoTalk_Photo_2024-10-24-23-55-55 003.jpeg&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;2992&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시에 세션이 진행되므로 모든 세션은 정리하지 못하고 참관했던 세션만 정리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 위주로 참관했고, 재밌어보이는 세션을 듣기도 했다. 근데 다 같은 컨퍼런스룸이라 하루종일 한 장소에만 있었다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;세션1. 대용량 트래픽 아니면 안 보셔도 됩니다! 선물하기 서비스 캐싱 전략&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이희관, 이세희 | 카카오&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_KakaoTalk_Photo_2024-10-24-23-55-55 002.jpeg&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QLpAF/btsKls5jz6B/7e4fA8EIfnz6QPIL5F95mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QLpAF/btsKls5jz6B/7e4fA8EIfnz6QPIL5F95mk/img.png&quot; data-alt=&quot;이 컨퍼런스룸을 떠나질 못했다...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QLpAF/btsKls5jz6B/7e4fA8EIfnz6QPIL5F95mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQLpAF%2FbtsKls5jz6B%2F7e4fA8EIfnz6QPIL5F95mk%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;550&quot; height=&quot;733&quot; data-filename=&quot;edited_edited_KakaoTalk_Photo_2024-10-24-23-55-55 002.jpeg&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2880&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이 컨퍼런스룸을 떠나질 못했다...&lt;/figcaption&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;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&quot;대용량 트래픽&quot;&lt;/b&gt; 백엔드 개발자의 눈길을 끄는 치트키. 마법의 용어.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;들은 세션 중 사람이 제일 많았던 세션이었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;로컬 캐시와 원격 캐시를 적절히 혼용해서 사용.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;자주 사용하고 범용적인 데이터는 로컬 캐시를 활용하고 캐시간의 동기화는 주키퍼 활용.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;데이터 변경이 일어나면 주키퍼를 통해 캐시를 만료 시켜서 새로운 캐시로 신선도 유지&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;다음 분 발표&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;배너 광고를 보여줄 캐싱 전략으로는 PER 알고리즘 전략을 쓰려고 했으나 채택하지 않았음.&amp;nbsp;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이유를 설명해주셨는데 기억이 잘 나지 않는다.(뒤에 설명을 생각하면 랜덤하게 갱신하지 않고 조회수에 따라 관리하고 싶었던거지 않을까)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;같이 보면 좋을 듯한 글(&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://blog.hwahae.co.kr/all/tech/14003&quot;&gt;PER&amp;nbsp; 알고리즘 구현&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;레디스의 sorted set 데이터구조를 활용해 프로모션과 누적 조회 수를 관리함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;key가 늘어나면 성능이 떨어지므로 시간대별로 파티션을 나누어 관리&lt;/li&gt;
&lt;li&gt;스코어 가중치(누적 조회수가 인자라 했던 것 같다)를 계산해서 캐시 갱신 관리&lt;/li&gt;
&lt;li&gt;가용성을 높이기 위해 먼저 로컬 스토리지에 담아두고 일정시간마다 저장함(로컬 스토리지를 버퍼 라이터 역할로 사용한다는 듯)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 데이터인지는 써두질 못했는데 정황상 누적 조회수를 바로 레디스에 넣지 않는다는 얘기 아니었을까&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;갱신 트리거로 rabbitMQ 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&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;span style=&quot;color: #000000;&quot;&gt;통계를 이용해 이탈을 방지할 수 있을까? SMART STATS 개발기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;송대섭 | 카카오게임즈&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게임의 체감 난이도는 국가마다 다르다. 어떤 나라에는 어려워도 다른 나라에는 너무 쉬울 수 있다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;적정 난이도를 맞추기 어렵다&amp;nbsp;&lt;/li&gt;
&lt;li&gt;국가는 말안해주셨지만, 왠지 한국인은 잘하고 서양인은 못한다일거 같은 느낌적인 느낌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;게임이 어려워서 이탈하는 경우 런칭 초기일수록 빠르게 대응해야한다&lt;/li&gt;
&lt;li&gt;직접적으로 도움을 준다(아이템 지원) -&amp;gt; 실질적으론 효과가 없다&lt;/li&gt;
&lt;li&gt;방치하면 탈퇴하고 지원해준다고 도움이 되진 않는다&lt;/li&gt;
&lt;li&gt;통계를 통해 해결방법을 찾고자 했다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;평균에서 벗어난 플레이를 하는 사람들이 주로 탈퇴했다&lt;/li&gt;
&lt;li&gt;방향을 못잡는 초보에게 도움을 줄 수 있지 않을까&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;유저에게도 통계를 제공하는 서비스를 만듦&lt;/li&gt;
&lt;li&gt;통계를 통해 초보는 방향성을 찾고 고수는 자신의 수준을 알 수 있다&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;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;언제, 어떻게, 어떤 통계를 제공할 것인가&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&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;color: #333333;&quot;&gt;메인 퀘스트나 던전 클리어 시점, 내가 지금 막혀있는 퀘스트&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;-&amp;gt; 통과를 위한 필요한 스펙을 알 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&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;color: #333333;&quot;&gt;나와 비슷한 사람의 데이터(통계에서 핵과금러, 봇, 매크로는 빼버림)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;유사한 플레이 성향으로 분류(안정지향적 vs 도전지향적, 솔플 vs 파티플)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;노력으로 따라할 수 있는 행동(유저가 과금 유도로 해석하지 않도록)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&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;color: #333333;&quot;&gt;확장성과 유연성을 고려해야했음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;확장성: 게임 내에 다른 게임이 추가될 수 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;유연성: 이벤트 기반으로 발생하는 로그를 수집(정해진 스키마로 로깅하기 보다는)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;적절한 시점에 통계에 대한 알림을 주고 싶었다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;데이터가 없어서 머신러닝을 시킬수 없었음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그래서 1차 스펙으로 데이터를 쌓고 2차에 제대로 써보자의 기조로 출발&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;=&amp;gt; 적절하다 싶은 타이밍에 A/B 테스트를 통해 알림을 보내봄&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;결론은 통계를 제공해준 유저들이 포기하는 허들을 넘어서 유지하는 경우가 많았다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&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;span style=&quot;color: #000000;&quot;&gt;모두를 위한 게임 데이터 검색 시스템&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;강동진, 유선정 | 카카오게임즈&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;게임 데이터 분석은 속도가 생명이다.(그만큼 중요하다)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;어뷰징 차단, 초기 결제가 매출에 이어져서 결제 패턴 분석 중요&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;BI(Business Intelligence: &lt;span style=&quot;text-align: left;&quot;&gt;조직이 더 나은 의사결정을 내리고, 정보를 기반으로 행동을 취하고, 보다 효율적인 비즈니스 프로세스를 구현할 수 있게 해주는 역량을 의미&lt;/span&gt;) 담당자들이 데이터 분석이 빠르게 필요함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;해결방안으로 데이터를 담당자들이 직접 사용할 수 있도록 오픈했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하지만, sql이나 데이터 구조를 배워야해서 막상 원활하지 않았다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;=&amp;gt; 자연어 기반 게임 데이터 검색 시스템을 만들었다(대략 이해한 바로 사람이 평소 쓰는 말을 사용해도 알아먹는 시스템?인듯)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;자연어 기반 게임 데이터 검색 시스템&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;기능 1. 데이터 추출&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;첫번째 시도: Text to SQL. 자연어를 입력하면 쿼리로 변환해줌&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;문제점: LLM의 Hallucination&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;AI 할루시네이션은 AI 모델이 생성하는 잘못되거나 오해의 소지가 있는 결과를 말함. 이러한 오류는 불충분한 학습 데이터, 모델의 잘못된 가정, 모델 학습에 사용된 데이터의 편향 등 다양한 요인으로 인해 발생할 수 있음.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;정확도가 보장되지 않아서 의미가 없다&amp;hellip;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;두번째 시도: 정확성 담보를 위해 쿼리 템플릿 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;문제점: 사용자가 원하는 템플릿이 없다면?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;해결방안: 쿼리 빌더 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;기능 2. 데이터 탐색&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;템플릿과 빌더 방식으로 해결 안되는 부분이 많음(사람이 하는 질문은 추상적이고 정형화 하기 어려운게 많다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;질문을 정형화하지 않고 직관에 의해 질문 해버린다면?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. 데이터를 쌓는데 정형화된 스키마에 맞춰서 쌓지 않는다.(NoSQL에다 로그 쌓듯이 쌓는다는 말인가?)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2. 게임 유저 중심의 질문이 많음&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;여러 테이블에 필요한 특성을 모아 유저 프로필을 만든다. 벡터 DB를 사용한다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;유사도를 계산해서 인간 친화적 질문의 문제를 처리할 수 있지만, 모든 질문을 해결하진 못한다는 한계가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&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;span style=&quot;color: #333333;&quot;&gt;GraalVM 도입을 JVM 백엔드 애플리케이션의 구동 초기 성능 문제 해결하기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;김동현 | 카카오&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;s&gt;발표자분이 떨렸는지 중간 중간 소리나게 한숨 쉬어서 귀여웠음.&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;이슈&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;서버가 웜업이 안되서 발생하는 이슈가 있었다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(기존의 일반적인 JVM은 실행하는 순간에 바이너리코드로 변환(JIT 컴파일)하기 때문에 기동 초기에 성능이 안나오는 많이 알려진 이슈)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;HotSpot JVM과 GraalVM 비교&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;JIT를 사용하는 HotSpot JVM보다 GraalVM이 런타임 환경에서 빠르다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;왜냐면 GraalVM은 컴파일 때 미리 바이너리코드로 다 바꿔두기 때문.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;단점으로 동적으로 런타임에 생성되는 코드에 대한 대응은 할 수 없음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;예시)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바 리플렉션을 통한 코드 생성&lt;/li&gt;
&lt;li&gt;로그백처럼 설정에서 구체적인 클래스 경로를 입력하는데 로그백은 해당 클래스를 동적으로 생성하기 때문에 ClassNotFound 발생한다&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;위에서 설명한 GraalVM의 단점 해결 방법은?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;동적으로 생성되는 것들을 컴파일 타임에 모두 만든다 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;=&amp;gt; Reachability Metadata라고 한단다. Tracing Agent를 통해 Reachability Metadata를 찾아낸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;Reachability Metadata를 얻으려면?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. Tracing Agent를 적용한 상태로 프로그램 시작, 종료&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2. Tracing Agent를 적용한 상태로, 높은 커버리지로 작성된 테스트 프로그램을 돌린다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&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;color: #333333;&quot;&gt;초반에는 GraalVM보다 처리율이 떨어지나 최종적으로는 HotspotJVM이 최적화되어 처리율이 더 좋았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;GraalVM은 처음부터 끝까지 안정화된 상태긴하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;시간이 지날수록 GraalVM이 HotspotJVM보다 처리율이 떨어지는데 GraalVM을 최적화할 수 있는 옵션(PGO: profile-guided optimization)을 적용하니 처리율도 높아졌다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;소감: GraalVM으로 웜업이 안된 이슈를 해결할 수 있으나 손이 가는게 많다로 느꼈음&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&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;span style=&quot;color: #333333;&quot;&gt;지연이체 서비스 개발기: 은행 점검 끝나면 송금해드릴게요!&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;박소현 | 카카오페이&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;기존에도 지연이체 서비스가 있었지만, 사내에서 rabbitMQ을 사용하고 있었다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;어느날, 전사 정책으로 kafka로 바꿔야했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&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;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;파티션이 여러 개인 상황에서&amp;nbsp;&lt;/span&gt;프로듀서가 메시지를 중복 발행하면 2개 이상의 컨슈머가 중복 처리할 수 있기 때문에 락을 사용했다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;유저에 대한 락을 걸려고 하니 유저마다 지연이체를 복수로 걸 수 있어서 유저락을 걸면 여러개의 지연이체 중 하나만 처리될 수도 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;=&amp;gt; 카프카 파티션 키를 사용해 하나의 컨슈머에서 하나의 유저에 대한 지연 이체를 처리하도록 함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&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;color: #333333;&quot;&gt;&lt;b&gt;=&amp;gt; 처리한 방법&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. 파티션과 컨슈머를 늘렸음(파티션 갯수와 컨슈머 수는 일치하도록 - 요건 카프카 권장사항이다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2. 카프카 메시지를 건바이건으로 가져오지 않고 배치 사이즈로 메시지를 가져오도록 함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;3. 컨슈머 1개당 패러럴하게(병렬처리) 송금처리함&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&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;span style=&quot;color: #333333;&quot;&gt;카카오페이는 어떻게 수천만 결제를 처리할까?&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;양세열 | 카카오페이&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;안전한 결제를 위해 동시성 이슈 처리가 필요하다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;요즘은 MSA 환경이다. &lt;u&gt;여러 시스템이 물려있는 트랜잭션 처리를 위해서는 DB락보단 &lt;b&gt;분산락&lt;/b&gt;&lt;/u&gt;을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;분산락 구현을 위해 Redis와 Spring을 사용하므로 자바 레디스 클라이언트인 Redisson 라이브러리를 사용했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;여러 종류의 락을 만들어서 사용하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;예를 들어, 유저에 대한 락이나 결제 ID 분산락 등&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&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;color: #333333;&quot;&gt;&lt;b&gt;1. Spring AOP 사용해서 애너테이션으로 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;AOP를 활용해 애너테이션으로 쓰면 불편한 점&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;적용이 어려움&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt; AOP를 적용하려면 프록시 객체를 만들 수 있어야해서 public 메소드에만 적용됨.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;락의 key를 파라미터로 사용하려면 적용이 어려움&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt; String으로 보통 받을텐데 잘못된 값이 들어와도 인지하기 어려움(런타임에 알게 되겠지?)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;락 장시간 점유로 불리한 성능&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;락이 불필요한 영역이 있는데 같이 묶여버림&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;불필요한 영역까지 락이 걸려있어 응답시간이 늦어짐&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;기능 수정 및 리팩터링 주의 필요&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;함수의 첫번째 파라미터를 락 키로 잡기로했는데 리팩토링 한다고 파라미터를 추가하면 락 키가 잘못 적용됨&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;2. 해결방법 - &lt;span style=&quot;text-align: start;&quot;&gt;함수형 분산락 사용하기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;필요한 곳에만 적용할 수 있도록 Util 성 함수처럼 만든다. (&lt;span style=&quot;text-align: start;&quot;&gt;분산락 함수의 인자를 적용할 함수로 받아서 실행한다.&lt;/span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;무슨 락을 거는지 알 수 있도록 함수를 락의 종류마다 분리한다. (함수명에 'userLock' 같은 식으로)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;장점&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;락이 필요한 상황에만 적용할 수 있고 private 함수에도 적용할 수 있다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;큰 트래픽을 처리한다면 필요한 부분만 락을 적용함으로써 불필요한 로직까지 락을 걸었을 때 비해서 성능이 훨씬 좋아진다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;나도 회사에서 분산락을 사용하고 있다. 내부사정으로 DB에 유니크 키를 걸 수 없어서 애플리케이션 단에서 유일성 보장을 해야했다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;카카오페이 결제에 비하면 아주아주 작은 트래픽을 받고 있는 서비스지만.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;난 반대로 함수형으로 사용하다 애너테이션으로 변경했다. 스펙과 요구사항이 달랐다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 서비스의 걸쳐져 있지 않아서 락이 긴 시간 걸릴 부분이 없었다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대부분 서비스에서 사용하는 DB의 여러 테이블&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;락을 사용하는 클래스마다 락을 제공하는 클래스의 의존성 선언을 여기저기 선언하기 싫었다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring의 dependency Injection을 하려면 생성자에 사용하는 클래스를 선언해주어야하니까&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;동시성이 보장되어야하는 부분이 서비스 내에 많지 않아서 락 키가 헷갈릴 일이 없었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;선택은 각 개발자의 몫으로..&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&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;span style=&quot;color: #000000;&quot;&gt;카카오톡 펑 개발기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;김중선, 서상민 | 카카오&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;펑을 개발할 때 푸시, 풀 모델에 대해 고민을 했었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(푸시, 풀 모델을 설명할 때 메시지큐 시스템(예: RabbitMQ)과&lt;/span&gt; &lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;a style=&quot;color: #0593d3;&quot; href=&quot;https://zorba91.tistory.com/291&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;발행구독&lt;/a&gt;&lt;/span&gt; &lt;span style=&quot;color: #333333;&quot;&gt;시스템(예: Kafka)으로 설명하곤 한다)&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;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;푸시&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;생성은 느리지만 조회가 빠름&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;공간적 특징 -&amp;gt; 1:N&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;풀&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;생성이 빠르지만 조회가 느림&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;공간적 특징 -&amp;gt; 1:1&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&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;color: #333333;&quot;&gt;방법: 생성자가 펑을 만들어서 조회자 피드로 푸시해서 조회자 피드에 펑을 저장&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;장점: 미리 저장해뒀기 때문에 조회자가 빠르게 조회할 수 있다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;단점: 조회자가 많다면 서버는 더 많은 일을 해야한다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;24시간 안에 조회 안하면 쓸모없이 데이터를 쌓은 게 되버린다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&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;color: #333333;&quot;&gt;방법: 조회 시점에 피드를 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;장점: 필요할 때만 데이터를 생성한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;단점: 조회시간이 오래걸린다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&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;color: #333333;&quot;&gt;sns를 사용하면 조회가 97%다. 생성은 3%&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;푸시 모델을 사용할 강력한 근거가 된다.(그래야 조회가 빠르니깐)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아직 펑에서 인플루언서가 있진 않은데 인플루언서가 있었다면 푸시 모델도 쉽진 않았을 것이다.(일반적인 조회자보다 훨씬 많은 조회자에게 푸시해야해서)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;조회자 피드에 본문을 다 넣어주는게 아니라 펑 본문 1개를 db에 저장하고 그 id 값만 조회자들 피드에 넣어준다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&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;color: #333333;&quot;&gt;펑은 수정은 못하고 &lt;u&gt;생성, 삭제&lt;/u&gt;만 된다는 조건을 활용해 &lt;u&gt;캐싱을 사용&lt;/u&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;펑 본문 DB를 조회하는 경우는 클라이언트에 캐싱되지 않은 id를 in 절로 검색한다.(캐싱된 펑은 제외됨)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;펑 피드는 언제 변할까?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;=&amp;gt; 펑을 생성할 때 또는 삭제할 때&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;두 이벤트가 발생하지 않는다면 DB를 조회할 필요도 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;리비전 개념을 사용해서 클라이언트와 서버 간의 버전 차이가 있을 때만 캐시를 동기화한다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(리비전 DB와 공개대상DB(누구에게 게시물을 공개할지 저장해두는 DB인듯), 펑 본문 DB를 분리되어있다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;리비전 DB에서 조회한 버전 값이 같다면 본문 DB를 볼 필요 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(이 얘기를 듣고 낙천적 Locking이 떠올랐다.)&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;펑 생성의 느린 원인&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;펑 본문 DB 생성은 빨라도 공개해야할 대상에 대한 정보를 가진 공개 대상 DB, 리비전 DB에서 부하가 발생한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;방안&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;공개대상 DB, 리비전 DB에 대한 정보 저장은 꼭 sync일 필요 없어서 async 처리했다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;펑 본문 DB 저장만 성공하면 생성자에게는 성공 응답을 준다&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&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;color: #333333;&quot;&gt;카카오톡 프로필의 경우, 기본 배경 이미지에 각각 이모지 위치를 저장해놓고 이에 맞춰서 렌더링 해주고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;방안&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;펑은 수정 기능이 없기 때문에 처음부터 통이미지로 만들어서 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하지만, 이미지에 인터랙션이 들어가면(인스타에 실시간 투표하기 같은?) 통이미지도 어렵다.&amp;nbsp;이럴 경우에는 카카오톡 프로필처럼 인터랙션 부분만 따로 처리한다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-10-24-23-55-54 001.jpeg&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;2992&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ms29I/btsKjrrode2/woaaw9bzjQTtXT6wqMykCK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ms29I/btsKjrrode2/woaaw9bzjQTtXT6wqMykCK/img.jpg&quot; data-alt=&quot;셔틀 타기 전 노을이 이뻐서 찍어봤다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ms29I/btsKjrrode2/woaaw9bzjQTtXT6wqMykCK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMs29I%2FbtsKjrrode2%2Fwoaaw9bzjQTtXT6wqMykCK%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;2992&quot; height=&quot;2992&quot; data-filename=&quot;KakaoTalk_Photo_2024-10-24-23-55-54 001.jpeg&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;2992&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;span style=&quot;color: #333333;&quot;&gt;후기를 좀 검색해보니 실망했다는 리뷰들이 꽤 보인다. 첫 기업의 기술 컨퍼런스 참석(지원은 맨날 하지만 맨날 떨어짐 ㅜ)이라 비교군이 없어서 잘 모르겠지만 만족스러웠다. 금방 말한 것과 같이 그 만족이 ifKakao라서기보다는 '컨퍼런스 참여가 중요하다'를 느낀 만족감이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;회사 일만 하는 것에 머물러 있으면 몰랐을 기술, 문제와 해결방안, 트렌드, 회사 외 사람들의 사고에 대해 알 수 있는 기회를 가진 것이 좋았던 컨퍼런스였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;짧게 쓰려고 했는데, 정리한 것들을 빠짐없이 쓰려다보니 길어져서 필요한 부분만 볼 수 있도록 줄임말로 처리하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 일기장/직장 생활</category>
      <category>2024 if(kakaoai)</category>
      <category>2024 ifkakao</category>
      <category>if(kakaoai)</category>
      <category>ifkakao</category>
      <category>이프카카오</category>
      <author>희랍인 조르바</author>
      <guid isPermaLink="true">https://zorba91.tistory.com/375</guid>
      <comments>https://zorba91.tistory.com/375#entry375comment</comments>
      <pubDate>Mon, 28 Oct 2024 04:09:30 +0900</pubDate>
    </item>
    <item>
      <title>[Kotest, Mockk, JUnit] 테스트 중 자주하는 착각 모음</title>
      <link>https://zorba91.tistory.com/374</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;업무 중에 테스트 코드를 작성하다가 착각해서 잘못 사용해뒀던 것들을 정리하기.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;알아두면 테스트 코드 작성하다 왜 안되지? 왜 이상하지하는 시간을 줄여줄 것이다! 별거 아닌 것처럼 보이는지만 원인을 알아채는데도 시간을 꽤 썼다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;929&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPoeLx/btsFF7ZX5vg/IAIsGQB3b7eOgFQikKKyn0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPoeLx/btsFF7ZX5vg/IAIsGQB3b7eOgFQikKKyn0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPoeLx/btsFF7ZX5vg/IAIsGQB3b7eOgFQikKKyn0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPoeLx%2FbtsFF7ZX5vg%2FIAIsGQB3b7eOgFQikKKyn0%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;929&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;929&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;1. Mockk 사용 시, Mocking 하지 않았는데, Mock 함수를 호출한다.&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;실제 인스턴스를 생성하더라도 mockk 기능을 쓰면 해당 객체의 함수 리턴 결과를 조작할 수 있을거라 생각하지만 그렇지 않다.&amp;nbsp;&lt;b&gt;mock 처리된 객체만 mockk 기능이 동작한다.&amp;nbsp;&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size18&quot; style=&quot;text-align: left;&quot;&gt;&lt;b&gt;착각하는 예시&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// 클래스
class Confusion {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun getHello(language: String): String {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return when(language) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;kr&quot; -&amp;gt; &quot;안녕&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else -&amp;gt; &quot;Hello&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

// 테스트
@SpringBootTest
class ConfusionTest: BehaviorSpec({

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Given(&quot;각 나라에 맞춰 인삿말을 가져온다&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val confusion = Confusion()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;every {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;confusion.getHello(&quot;kr&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} returns &quot;안녕&quot;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;When(&quot;Hello 함수 호출&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val result = confusion.getHello(&quot;kr&quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Then(&quot;Hello를 반환 받는다&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result shouldBe &quot;안녕&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
})&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D7qF0/btsFIjL70jw/qc4hflnmD6QiKe79YVFfVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D7qF0/btsFIjL70jw/qc4hflnmD6QiKe79YVFfVk/img.png&quot; data-alt=&quot;에러 발생&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D7qF0/btsFIjL70jw/qc4hflnmD6QiKe79YVFfVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD7qF0%2FbtsFIjL70jw%2Fqc4hflnmD6QiKe79YVFfVk%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;1476&quot; height=&quot;481&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;에러 발생&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;수정한 예시&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@SpringBootTest
class ConfusionTest: BehaviorSpec({

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Given(&quot;각 나라에 맞춰 인삿말을 가져온다&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	// mocking 처리해준다
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val confusion = mockk&amp;lt;Confusion&amp;gt;()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;every {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;confusion.getHello(&quot;kr&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} returns &quot;안녕&quot;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;When(&quot;Hello 함수 호출&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val result = confusion.getHello(&quot;kr&quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Then(&quot;Hello를 반환 받는다&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result shouldBe &quot;안녕&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
})&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVuLhY/btsFGKiUw2F/m5uCR0x0WR7tQ4wKpbhut1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVuLhY/btsFGKiUw2F/m5uCR0x0WR7tQ4wKpbhut1/img.png&quot; data-alt=&quot;성공&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVuLhY/btsFGKiUw2F/m5uCR0x0WR7tQ4wKpbhut1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVuLhY%2FbtsFGKiUw2F%2Fm5uCR0x0WR7tQ4wKpbhut1%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;1114&quot; height=&quot;201&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;성공&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;간단한 예시로 해서 그렇지만, 실제 인스턴스와 mock 객체를 혼용하면서 테스트 코드를 쓸 경우 이런 경우가 생길 수 있다. 또, 실제 인스턴스는 mockk 기능을 쓸 수 없단걸 모르면 계속 헤맬 수 있다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;2. kotlinfixture 사용시, 찐으로 랜덤한 Number를 만들어내기 위해 범위를 굉장히 넓게 잡는다.&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;이 부분은 인텔리J에서 제공하는 profiler로&amp;nbsp; 찾아냈다. 전체 테스트를 돌릴 때마다 OOM이 떨어지니 정확히 어디서 발생하는지 알기 어려웠는데 kotlinfixture로 큰 범위를 만들어내는 부분에서 발생한다는 걸 찾았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;kotlinfixture에서 랜덤한 숫자를 뽑을 때 뭔가 마법 같이 랜덤한 숫자를 바로 꼽는게 아니다.&amp;nbsp;&lt;u&gt;정해진 범위만큼의 리스트는 인스턴스화&lt;/u&gt;&amp;nbsp;시키고 거기서 랜덤하게 뽑는다.&amp;nbsp;&lt;b&gt;결국 범위를 아주 크게 잡으면 OOM이 떨어질 수 있다는 것이다.&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;보통 로컬 PC(개발자 본인 PC)는 메모리가 짱짱하고 CPU 성능 좋은 맥북이 보통이라 로컬에서 테스트 돌릴 때는 알아채지 못하고, 젠킨스처럼 CI를 해주는 (맥북보다 성능이 떨어지는)서버에서 테스트가 돌다가 깨져버린다. 그래서 자신의 PC에서는 잘 되는데 뭐가 문제인지 헤맬 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;문제발생 예시&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class ConfusionTest: BehaviorSpec({

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val fixture = kotlinFixture()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Given(&quot;모든 숫자를 더한다&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val input = (1..100).map {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fixture(1..100_000_000_000)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;When(&quot;sum 함수 호출&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val result = input.sum()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Then(&quot;총 합을 반환받는다&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result shouldBe input.reduce { acc, i -&amp;gt;&amp;nbsp;&amp;nbsp;acc + i }
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
})&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1345&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWht0o/btsFGA170GI/RjxUYLyIeK5tLh0w0OYLQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWht0o/btsFGA170GI/RjxUYLyIeK5tLh0w0OYLQ1/img.png&quot; data-alt=&quot;OOM 발생&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWht0o/btsFGA170GI/RjxUYLyIeK5tLh0w0OYLQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWht0o%2FbtsFGA170GI%2FRjxUYLyIeK5tLh0w0OYLQ1%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;1345&quot; height=&quot;461&quot; data-origin-width=&quot;1345&quot; data-origin-height=&quot;461&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;OOM 발생&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;kotlinfixture(1..100_000_000_000)처럼 범위내에서 random한 숫자를 만들 때, 내부적으로 아래의 함수를 호출한다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H8419/btsFGL3dZQV/5KJKtQAmnweKiSPnwxCKG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H8419/btsFGL3dZQV/5KJKtQAmnweKiSPnwxCKG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H8419/btsFGL3dZQV/5KJKtQAmnweKiSPnwxCKG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH8419%2FbtsFGL3dZQV%2F5KJKtQAmnweKiSPnwxCKG1%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;786&quot; height=&quot;118&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;toMutableList에서 range만큼 리스트를 메모리에 올리는데 이 때 큰 범위의 리스트가 여러개 생기다보니 OOM이 발생한다.&lt;br&gt;OOM은 안 나더라도 범위는 적당히 잡아주는게 테스트 성능에 영향을 주지않을 것이다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;3. @MockkBean 또는 @MockkBean을 사용하더라도  Spring Container는 한번만 띄워질 것이다.&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;@MockBean 또는 @MockkBean을 사용할 경우, ApplicationContext를 재활용하지 못하고 Spring Container는 매번 reloading된다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그래서 이럴 경우, mock 객체를 만들어서 Bean으로 등록해서 주입받거나, 테스트 하는 클래스에서 mock 객체를 직접 만들어서 사용하는게 낫다. (@MockkBean, @MockBean은 그냥 쓰지 않는게 편하다)&lt;br&gt;&amp;nbsp;&lt;br&gt;그 이유는&amp;nbsp;&lt;span style=&quot;color: #0593D3;&quot;&gt;여기서&lt;/span&gt;&amp;nbsp;설명하고 있다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;주된 이유는 이러하다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;The Spring test framework will cache an ApplicationContext&amp;nbsp;whenever possible between test runs.&lt;br&gt;In order to be cached, the context must have an exactly equivalent configuration.&lt;br&gt;Whenever you use @MockBean, you are by definition changing the context configuration.&lt;br&gt;&lt;br&gt;스프링 테스트 프레임워크는 테스트가 돌아가면서 ApplicationContext를 캐싱할 것이다.&lt;br&gt;캐시하기 위해서는 context가 반드시 동일한 configuration을 가져야한다.&amp;nbsp;&lt;br&gt;@MockBean을 사용할때마다, context의 configuration은 변할 것이다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;그래서 @MockBean을 만날때마다 캐싱을 하지 못하고 reloading 하는 것이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;문제발생 예시&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@Service
class ConfusionService(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val dependencyService: DependencyService,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val otherDependencyService: OtherDependencyService,
) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun hello(): String {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dependencyService.doSomething()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;otherDependencyService.doSomething()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return &quot;Hello&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

@Service
class DependencyService {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun doSomething() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;println(&quot;do something by dependency&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

@Service
class OtherDependencyService {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun doSomething() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;println(&quot;do something by otherDependency&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@SpringBootTest
class ConfusionTest(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@MockkBean
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val dependencyService: DependencyService,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val confusionService: ConfusionService
) : BehaviorSpec() {

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Given(&quot;MockkBean 사용 시, ApplicationContext를 캐싱하지 않는지 테스트 한다&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;every {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dependencyService.doSomething()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} just Runs
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;When(&quot;함수 호출&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;println(confusionService.hello())

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Then(&quot;캐싱하지 않는다&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}


@SpringBootTest
class OtherConfusionTest(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@MockkBean
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val otherDependencyService: OtherDependencyService,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val confusionService: ConfusionService
): BehaviorSpec(){

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Given(&quot;MockkBean 사용 시, ApplicationContext를 캐싱하지 않는지 테스트 한다&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;every {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;otherDependencyService.doSomething()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} just Runs
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;When(&quot;함수 호출&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;println(confusionService.hello())

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Then(&quot;캐싱하지 않는다&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;이 테스트들을 돌리면 어떻게 될까?&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ ./gradlew -i clean test&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;위 커맨드를 날리게 되면,  Spring Container가 두 번 뜬 걸 볼 수 있다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1889&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDxuZN/btsFFM9GPi1/7Mx9OPrWjDNQ1XfVdMYyYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDxuZN/btsFFM9GPi1/7Mx9OPrWjDNQ1XfVdMYyYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDxuZN/btsFFM9GPi1/7Mx9OPrWjDNQ1XfVdMYyYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDxuZN%2FbtsFFM9GPi1%2F7Mx9OPrWjDNQ1XfVdMYyYK%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;1889&quot; height=&quot;788&quot; data-origin-width=&quot;1889&quot; data-origin-height=&quot;788&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;수정한 예시&lt;/b&gt;&lt;br&gt;외부로 통신해야할 의존성만 mocking해서 사용하는 편이다. 위 예시에서&amp;nbsp;&lt;b&gt;ConfusionTest&lt;/b&gt;는&amp;nbsp;&lt;b&gt;OtherDependencyService&lt;/b&gt;가 외부 api로 요청하는 서비스이고,&amp;nbsp;&lt;b&gt;OtherConfusionTest&lt;/b&gt;는&amp;nbsp;&lt;b&gt;DependencyService&lt;/b&gt;가 외부 api로 요청하는 서비스라 mocking한다고 가정해보자.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@SpringBootTest
class ConfusionTest(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dependencyService: DependencyService,
) : BehaviorSpec() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val otherDependencyService = mockk&amp;lt;OtherDependencyService&amp;gt;()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val confusionService = ConfusionService(dependencyService, otherDependencyService)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Given(&quot;MockkBean 사용 시, ApplicationContext를 캐싱하지 않는지 테스트 한다&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;every {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;otherDependencyService.doSomething()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} just Runs
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;When(&quot;함수 호출&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;println(confusionService.hello())

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Then(&quot;캐싱하지 않는다&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}


@SpringBootTest
class OtherConfusionTest(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;otherDependencyService: OtherDependencyService,
): BehaviorSpec(){
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val dependencyService = mockk&amp;lt;DependencyService&amp;gt;()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private val confusionService = ConfusionService(dependencyService, otherDependencyService)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Given(&quot;MockkBean 사용 시, ApplicationContext를 캐싱하지 않는지 테스트 한다&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;every {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dependencyService.doSomething()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} just Runs

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;When(&quot;함수 호출&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;println(confusionService.hello())

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Then(&quot;캐싱하지 않는다&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1868&quot; data-origin-height=&quot;895&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmUnTC/btsFFNU3wP4/deEGS2ikIhVnj6TZXx5xJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmUnTC/btsFFNU3wP4/deEGS2ikIhVnj6TZXx5xJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmUnTC/btsFFNU3wP4/deEGS2ikIhVnj6TZXx5xJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmUnTC%2FbtsFFNU3wP4%2FdeEGS2ikIhVnj6TZXx5xJk%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;1868&quot; height=&quot;895&quot; data-origin-width=&quot;1868&quot; data-origin-height=&quot;895&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;Spring Container가 딱 한 번만 뜬 것을 볼 수 있다.&lt;/p&gt;</description>
      <category>웹 개발/Spring Framework</category>
      <category>JUnit</category>
      <category>Kotest</category>
      <category>kotlinfixture</category>
      <category>mock</category>
      <category>mockk</category>
      <category>spring</category>
      <category>spring test</category>
      <category>test 실수</category>
      <author>희랍인 조르바</author>
      <guid isPermaLink="true">https://zorba91.tistory.com/374</guid>
      <comments>https://zorba91.tistory.com/374#entry374comment</comments>
      <pubDate>Tue, 12 Mar 2024 00:27:30 +0900</pubDate>
    </item>
    <item>
      <title>6년차 중니어(?) 백엔드 개발자의 2023 회고</title>
      <link>https://zorba91.tistory.com/370</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;어느새 한 해가 흘렀다. 벌써 만으로 5년차 개발자가 되었다.&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;현재 다니는 회사는 내 길지 않은 커리어 중 가장 오래다닌 회사로 매일 갱신 중이다. 재직기간 2년을 돌파했기 때문!&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;/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;/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;/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;우선&amp;nbsp;순위&amp;nbsp;1.&amp;nbsp;&lt;s&gt;올해도&amp;nbsp;그렇지만&amp;nbsp;회사&amp;nbsp;업무가&amp;nbsp;1순위다.&amp;nbsp;작게&amp;nbsp;시작하지만&amp;nbsp;크게&amp;nbsp;키울&amp;nbsp;프로젝트를&amp;nbsp;맡아서&amp;nbsp;잘&amp;nbsp;해내고&amp;nbsp;싶다.&lt;/s&gt;&lt;br /&gt;맡은 일만큼은 최선을 다했고, 능동적으로 일을 찾고, 개선했기에 100%을 주고 싶다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;▶ 우선 순위 2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;s&gt;집필중인 책은 어떻게든 완성하자&lt;/s&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;60%&lt;br /&gt;&amp;nbsp;&lt;br /&gt;▶ 우선 순위 3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;s&gt;블로그에 짧게라도 1달에 유의미한 글 1개는 올리자&lt;/s&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;10개 정도 올렸으니 80%&lt;br /&gt;&amp;nbsp;&lt;br /&gt;▶&amp;nbsp;우선&amp;nbsp;순위&amp;nbsp;4.&amp;nbsp;&lt;s&gt;틈틈이&amp;nbsp;개발&amp;nbsp;책&amp;nbsp;읽기(1~2달에&amp;nbsp;한권씩)&lt;/s&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3권 완독, 1권 읽다가 포기, 3권 읽는 중이다. 4권 읽은걸로 해서 대략 40%&lt;br /&gt;&amp;nbsp;&lt;br /&gt;▶&amp;nbsp;우선&amp;nbsp;순위&amp;nbsp;5.&amp;nbsp;&lt;s&gt;Rust로&amp;nbsp;사이드&amp;nbsp;프로젝트로&amp;nbsp;서비스&amp;nbsp;올려보기(Rust를&amp;nbsp;알게&amp;nbsp;됐는데&amp;nbsp;정말&amp;nbsp;매력적이어서&amp;nbsp;꼭&amp;nbsp;공부해보고&amp;nbsp;싶다)&lt;/s&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;러스트가 모에요? 0%&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;총 달성률: 56%&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;물.jpeg&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5Fma6/btsDj2MHvi6/NKSdXTwTyZ0eTTLWIbCMo1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5Fma6/btsDj2MHvi6/NKSdXTwTyZ0eTTLWIbCMo1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5Fma6/btsDj2MHvi6/NKSdXTwTyZ0eTTLWIbCMo1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5Fma6%2FbtsDj2MHvi6%2FNKSdXTwTyZ0eTTLWIbCMo1%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;405&quot; height=&quot;720&quot; data-filename=&quot;물.jpeg&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;물이 반이나 있네? 물이 반 밖에 안되네...? 노력하자 '나'란놈ㅜㅜ&lt;/p&gt;
&lt;hr data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;상반기&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2023년이 시작하면서 같은 파트에 세 분이나 퇴사를 했다. (몇달 뒤 새로운 분들이 합류하시긴 했다.)&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;/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;/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;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;자동차 검사&lt;/b&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;자검 스크린샷.jpeg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0w48L/btsDlonWfVN/kP9sYDmEkHgiPwmCFgou10/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0w48L/btsDlonWfVN/kP9sYDmEkHgiPwmCFgou10/img.jpg&quot; data-alt=&quot;자동차 검사는 카카오 T, 카카오 내비에서!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0w48L/btsDlonWfVN/kP9sYDmEkHgiPwmCFgou10/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0w48L%2FbtsDlonWfVN%2FkP9sYDmEkHgiPwmCFgou10%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;250&quot; height=&quot;611&quot; data-filename=&quot;자검 스크린샷.jpeg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2640&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;자동차 검사는 카카오 T, 카카오 내비에서!&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;/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;기획 1, 디자인 1, 백엔드 1, 프론트엔드 1 이렇게 구성하여 일 했는데, 일 욕심 많은 사람들이라 피드백이 빠르고 진행 속도가 빨랐다.&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 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;기술적으로는 서비스 내에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;이벤트&lt;/b&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;내부적으로 객체 상태 관리, 크리티컬한 비즈니스 로직을 제외한 부가적인 처리들은 분리하고 싶었다. 이벤트를 활용하지 않아도 트랜잭션에 영향을 안 줄 수 있겠지만, 그렇게 처리하려면 메서드 분리와 try, catch로 잡아서 계속 진행하도록 만들어야해서 그런 처리를 넣는게 싫었다. 크리티컬하지 않은데도 해당 로직을 수행하느라 응답이 늦어지는 것도 싫었다.&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;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;스프링 내부적으로 이벤트를 publish/listen하게 해주는 기능&lt;/u&gt;이 있었다. 내부에서만 흐르는 이벤트라면 스프링 이벤트를 활용하는게 훨씬 안정적일거라&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;스프링 이벤트&lt;/b&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또, 카프카는 전사적으로 사용하는거라 내부에서 쓸 이벤트 때문에 topic과 partition을 만든다면 배보다 배꼽이 더 커지는 개발 방법이다.&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;/p&gt;
&lt;hr data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;하반기&lt;/h3&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;하반기에는  그룹사 전체적으로 대외 이슈가 많아 뭐든 프로젝트나 서비스 오픈이 조심스러웠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-09 오후 11.49.46.png&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;323&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcpkmO/btsDjYcnvNE/5WktFCd1kid4Yolicp84Bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcpkmO/btsDjYcnvNE/5WktFCd1kid4Yolicp84Bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcpkmO/btsDjYcnvNE/5WktFCd1kid4Yolicp84Bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcpkmO%2FbtsDjYcnvNE%2F5WktFCd1kid4Yolicp84Bk%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;548&quot; height=&quot;323&quot; data-filename=&quot;스크린샷 2024-01-09 오후 11.49.46.png&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;323&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Bottom Up으로 시작한 소규모 프로젝트가 있었는데,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;한달간&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&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;/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;/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;b&gt;1. 테스트 커버리지 높이기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;테스트 커버리지가 얼마나 되는지 확인해보기 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Jacoco를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;적용했다. 아마 처음에 30%? 정도 였던 것 같다.&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;/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;span style=&quot;color: #0593d3;&quot;&gt;&lt;a style=&quot;color: #0593d3;&quot; href=&quot;https://toss.im/slash-21/sessions/1-6&quot;&gt;토스 테스트 커버리지 100%&lt;/a&gt;&lt;/span&gt;)을 보았었는데, 속으로 '쩌..쩐다'라 생각했었고, 때마침 하반기 들어 개발 잘하는 친구와 이야기하다 본인은 테스트 커버리지 60% 이하로 떨어뜨린적이 없단 얘기를 들었다.&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;나도 감으로 테스트 코드를 작성할게 아니고, 정량적으로 개발해야겠단 생각이 들었다. 최종적으로 58%까지 올리고 한해를 마무리 했다. 현재도 커버리지를 높여가는 중이다. (테스트 커버리지 70% 가즈아)&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;/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;b&gt;2. 기술 공유를 위한 문서화&lt;/b&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;&lt;b&gt;3. 안정성과 효율화&lt;/b&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;풀리퀘스트 생성 시, 작업한 브랜치를 기반한 빌드가 트리거되면서 테스트 커버리지가 기준을 넘지 못하면 빌드가 실패하게 만들었다.&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;span&gt;&amp;nbsp;&lt;/span&gt;CD(Continuous Delivery) 툴이 배포 히스토리를 통한 롤백, 블루/그린 또는 카나리 배포, 파이프라인 기능 등을 제공해줬기 때문에 관리가 잘 안되던 젠킨스를 버리고 파트 내에서 누구보다 빠르게 마이그레이션했다.&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;/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;/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;/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;b&gt;4. 서비스 개선하기&lt;/b&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;기획자분과 유저 편의를 높이기 위한 방안에 대해 의견이 부딪히면 논의를 굉장히 많이 했던 한 해였다. 서로를 설득하기 위한 의견 피력이 잦았지만, 기분이 상했던 적은 단 한번도 없었다. 이게 당연한 것 같지만, 실제로 일하다보면 그렇지 않은 경우를 허다하게 본다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;개인사&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2023년은 개발자보단 '나'란 사람의 개인적인 도전이 많았던 해였다.&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;b&gt;복싱을 배우기 시작했다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-11 오전 1.38.37.png&quot; data-origin-width=&quot;515&quot; data-origin-height=&quot;337&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bH18Dl/btsDjW6GcI3/ELsZ4Vyk4LPetMNWPOX490/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bH18Dl/btsDjW6GcI3/ELsZ4Vyk4LPetMNWPOX490/img.png&quot; data-alt=&quot;이거슨 입에서 나는 소리가 아니여&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bH18Dl/btsDjW6GcI3/ELsZ4Vyk4LPetMNWPOX490/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbH18Dl%2FbtsDjW6GcI3%2FELsZ4Vyk4LPetMNWPOX490%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;515&quot; height=&quot;337&quot; data-filename=&quot;스크린샷 2024-01-11 오전 1.38.37.png&quot; data-origin-width=&quot;515&quot; data-origin-height=&quot;337&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이거슨 입에서 나는 소리가 아니여&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;어릴적부터 격투기를 배우고 싶었는데, 드디어 등록했다. 지난해 8월부터 꾸준히 주 2~3회 나가고 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1시간이면 그날 남은 체력까지 쏙 뽑아낼 수 있다. 몸은 확실히 이전보다 더 튼튼해졌다.&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;b&gt;대학생들을 대상으로 직업 강연을 했다.&lt;/b&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;3시간이라는 어마무시한 강연 시간을 준비해야해서 2주 동안 퇴근 후 슬라이드를 준비하는데 꼬박 매달렸다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;a style=&quot;color: #0593d3;&quot; href=&quot;https://zorba91.tistory.com/358&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;대학 강연 후기&lt;/a&gt;&lt;/span&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;b&gt;결혼식 사회를 봤다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;친한 대학 동기에게 결혼식 사회를 부탁받았다. 결혼식이 끝나고 결혼한 동기가 아까 긴장된다는 말은 설레발이었냐며 칭찬해주었다. 물론 친구 표정이 만족해보여서 나도 만족 :0)&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;b&gt;게임 시간 줄이기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;작년에 비해 게임하는 시간을 줄였다. 평일은 30분 할때도 있고 아예 안할 때도 있고.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;올해의 목표&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;▶ 개발 1~2달에 1권 읽기&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;▶ 블로그 포스팅 한 달에 최소 1개&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;▶ 집필중인 책 완성하기(1달에 목차 1개는 무조건 쓰자)&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;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;'이야. 내가 누구한테 질문받을 수준인가?'라며 기쁘면서도 부담을 느낀다. 어쨌든 신나서 어떻게든 찾아보고 고민하고 도와드린다.&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;/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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_송이.jpeg&quot; data-origin-width=&quot;2320&quot; data-origin-height=&quot;2320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pq2pd/btsDmoHZTsW/y5hKiTFpNNS7HBQUEwEk0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pq2pd/btsDmoHZTsW/y5hKiTFpNNS7HBQUEwEk0K/img.png&quot; data-alt=&quot;해맑아...!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pq2pd/btsDmoHZTsW/y5hKiTFpNNS7HBQUEwEk0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPq2pd%2FbtsDmoHZTsW%2Fy5hKiTFpNNS7HBQUEwEk0K%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;500&quot; height=&quot;500&quot; data-filename=&quot;edited_송이.jpeg&quot; data-origin-width=&quot;2320&quot; data-origin-height=&quot;2320&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;해맑아...!&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;/p&gt;</description>
      <category>개발 일기장/직장 생활</category>
      <category>2023 회고</category>
      <category>개발자 회고</category>
      <category>백엔드 회고</category>
      <category>회고</category>
      <author>희랍인 조르바</author>
      <guid isPermaLink="true">https://zorba91.tistory.com/370</guid>
      <comments>https://zorba91.tistory.com/370#entry370comment</comments>
      <pubDate>Thu, 11 Jan 2024 01:50:56 +0900</pubDate>
    </item>
    <item>
      <title>validation과 verification 차이가 무엇일까?</title>
      <link>https://zorba91.tistory.com/368</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-04 오전 2.29.46.png&quot; data-origin-width=&quot;771&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxkn3m/btsC0XrBV5u/ESO9v7tabl1aKedghkoCC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxkn3m/btsC0XrBV5u/ESO9v7tabl1aKedghkoCC0/img.png&quot; data-alt=&quot;출처: https://www.guru99.com/verification-v-s-validation-in-a-software-testing.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxkn3m/btsC0XrBV5u/ESO9v7tabl1aKedghkoCC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcxkn3m%2FbtsC0XrBV5u%2FESO9v7tabl1aKedghkoCC0%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;771&quot; height=&quot;226&quot; data-filename=&quot;스크린샷 2024-01-04 오전 2.29.46.png&quot; data-origin-width=&quot;771&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://www.guru99.com/verification-v-s-validation-in-a-software-testing.html&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;/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;딱히 관심을 안 가지다보니 어떤 메서드는 validate**, 어떤 메서드는 verify** 형태로 작성했다.&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;그 차이도 있겠지만 일관성 없는 코딩을 하고 있다는게 마음에 들지 않아서 그 차이를 살펴봤다.&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Validate&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;1. to make something officially acceptable or&amp;nbsp;approved, especially after&amp;nbsp;examining&amp;nbsp;it&lt;br /&gt;2. to prove that something is&amp;nbsp;correct&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 무언가를 공식적으로 허용하거나 승인한다. 특히, 그것을 검사한 후에&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2.  무언가를 옳은지 증명한다.&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Verify&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;1. to prove that something&amp;nbsp;exists or is&amp;nbsp;true, or to make certain that something is&amp;nbsp;correct&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. '무언가가 존재하는지 또는 참인지 증명하다' 또는 '무언가가 올바른가 확인한다'&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;비슷한듯, 안비슷한듯 하지만 차이점을 찾아보려하니, Verify가 무언가 더 기계적인 느낌이 든다. exist, is true라는 단어들에서 주는 뉘앙스에서 그렇달까. Validate는 증명을 해내야할 것 같고, acceptable한지 판단해야하는 느낌이고.&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;/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;/p&gt;
&lt;hr data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;키 포인트&lt;/b&gt;는 명세서 vs 요구사항&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;둘의 차이를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Validation&lt;/b&gt;은 '&lt;u&gt;Are you building the right thing?&lt;/u&gt;',&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Verification&lt;/b&gt;은 '&lt;u&gt;Are you building it right?'&lt;/u&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;'Building the right thing'은 유저의 요구에 맞게 올바른 걸 만들었는가&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;'Building it right'은 그것이 제대로 만들어졌는가, 명세서대로 시스템을 정확히 구현하였는가를 체크하는 것이다.&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;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Validation&lt;/b&gt;은 '&lt;u&gt;유저가 요구하는 사항에 맞춰 제대로 개발 했는가&lt;/u&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;b&gt;Verification&lt;/b&gt;은 '&lt;u&gt;명세서대로 개발되었는가&lt;/u&gt;'이다. 빌드가 제대로 되는지, 함수가 올바르게 동작하는지를 확인한다. 그래서 Verification 과정은 내부 프로세스인 경우가 많다.&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;단위 테스트나 통합 테스트처럼 테스트 코드를 작성하는 것도 일종의  Verification 과정이라 볼 수 있다. 빌드가 깨지진 않는지 함수가 명세서대로 동작하는지를 확인하기 때문이다.&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;QA 과정을 태우면서 UI 화면에서 직접 확인하는 테스트 같은 경우, Validation 과정이라 할 수 있다.&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;영단어 뜻에서도 보았듯이 Verification은 Validation보다 사전에 수행하는 프로세스이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-04 오전 1.16.35.png&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6hBFO/btsCTAxp6IO/qoMPtVic3nInv3KjOJrgJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6hBFO/btsCTAxp6IO/qoMPtVic3nInv3KjOJrgJK/img.png&quot; data-alt=&quot;출처: https://www.softwaretestinghelp.com/what-is-verification-and-validation&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6hBFO/btsCTAxp6IO/qoMPtVic3nInv3KjOJrgJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6hBFO%2FbtsCTAxp6IO%2FqoMPtVic3nInv3KjOJrgJK%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;751&quot; height=&quot;383&quot; data-filename=&quot;스크린샷 2024-01-04 오전 1.16.35.png&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;383&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://www.softwaretestinghelp.com/what-is-verification-and-validation&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Verification, Validation의 차이를 표로 정리해둔 글이 많은데, 제일 항목이 많은 표로 가져왔다.&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;아래 표에서 Verification 항목에서 static testing, not include execution of the code 이런 문구 때문에 '코드를 아예 안 돌린다는 뜻인가?'로 헷갈릴 사람들이 있을 듯하다.&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;Verification은 컴파일 환경까지, Validation은 런타임 환경에서 수행하는거라 생각하면 이해하기 좀 쉽다. 그래서 단위 테스트, 통합 테스트가 Verification 범주에 들어가는 걸 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;verification_vs_validation.png&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;1375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpjQlx/btsCTAD9IRD/tX6l2VOBmDoaeJVKurkx9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpjQlx/btsCTAD9IRD/tX6l2VOBmDoaeJVKurkx9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpjQlx/btsCTAD9IRD/tX6l2VOBmDoaeJVKurkx9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpjQlx%2FbtsCTAD9IRD%2FtX6l2VOBmDoaeJVKurkx9K%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;860&quot; height=&quot;1197&quot; data-filename=&quot;verification_vs_validation.png&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;1375&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;예시&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Validation 실패&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템에 버그는 없지만, 유저 요구사항에 맞지 않게 동작한다&lt;/li&gt;
&lt;li&gt;결제 성공 시, 결제 내역을 띄워주지 않고 홈 화면으로 이동한다.&lt;/li&gt;
&lt;li&gt;비회원이면 상품 목록을 보여주지 말고 회원가입 화면을 띄워주라고 했는데, 상품 목록이 보여진다&lt;/li&gt;
&lt;li&gt;내 주문 내역을 다른 유저도 마음대로 볼 수 있다&lt;/li&gt;
&lt;li&gt;패스워드를 잘못 입력했는데 로그인이 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Verification 실패&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt; 어떤 함수의 인수(argument)가 not null만 허용해야하는데, 값이 null이 넘어왔는데도 이상이 없었다.&lt;/li&gt;
&lt;li&gt;string으로 return 해야하는 함수가 int로 return한다.&lt;/li&gt;
&lt;li&gt;DB 테이블의 특정 컬럼이 정수만 허용하는데 소수점 데이터가 들어갔다&lt;/li&gt;
&lt;li&gt;유일성을 보장해야하는 데이터가 중복으로 존재한다&lt;/li&gt;
&lt;li&gt;조건문이 의도한대로 타지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; Verify는 Validate에 비해 상대적으로 객관적인 의미를 내포한 것으로 느껴진다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;메서드를 작성할 때 검증 메서드 prefix로 뭐가 맞을까 고민하다 여기까지 찾아보았다.&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;정리한걸 바탕으로 생각해보면, 개발자가  코드 작성에 있어서 검증을 위해 Validate**를 메서드의 prefix 쓸 일이 있을까 싶다. 모든건 함수와 값, 조건문 등 명세대로 동작하는지 검증하는 일들이라서다.&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;Validation의 실패 예시도 코드 레벨에서 검증할 것 처럼 보이지만, 최종적인 프로덕트에서 발생하는 이슈이고, 그 이전에 코드 레벨에서 함수가 의도한대로 동작하지 않았던 것이다. 모든 동작이 명세서대로 작동하지만, Validation 실패라고 여기려면 요구사항을 놓쳤거나 명세서에 제대로 반영하지 못했다는게 아닐까?&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;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;a href=&quot;https://www.arbourgroup.com/blog/2015/verification-vs-validation-whats-the-difference/&quot;&gt;https://www.arbourgroup.com/blog/2015/verification-vs-validation-whats-the-difference/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/differences-between-verification-and-validation/&quot;&gt;https://www.geeksforgeeks.org/differences-between-verification-and-validation/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;a href=&quot;https://www.softwaretestinghelp.com/what-is-verification-and-validation/&quot;&gt;https://www.softwaretestinghelp.com/what-is-verification-and-validation/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Verification_and_validation&quot;&gt;https://en.wikipedia.org/wiki/Verification_and_validation&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;a href=&quot;https://uk.indeed.com/career-advice/career-development/verification-vs-validation&quot;&gt;https://uk.indeed.com/career-advice/career-development/verification-vs-validation&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발 일기장/개발 일상</category>
      <category>validate verify 차이</category>
      <category>validate vs verify</category>
      <category>validation verification 차이</category>
      <category>validation vs verification</category>
      <author>희랍인 조르바</author>
      <guid isPermaLink="true">https://zorba91.tistory.com/368</guid>
      <comments>https://zorba91.tistory.com/368#entry368comment</comments>
      <pubDate>Thu, 4 Jan 2024 03:01:40 +0900</pubDate>
    </item>
    <item>
      <title>[Spring boot + JPA] Hibernate 성능 분석을 위한 로깅하기</title>
      <link>https://zorba91.tistory.com/366</link>
      <description>&lt;h2 id=&quot;id-하이버네이트성능분석하기-1.하이버네이트세션관련해서걸린시간들을통계내기&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. 하이버네이트 세션 관련해서 걸린 시간들을 통계내기&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* 주의: 성능 이슈를 유발하므로 운영환경에선 쓰지말자.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre id=&quot;code_1703752271998&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// application.yaml
spring.jpa.properties.hibernate.generate_statistics=true

// logback.xml
&amp;lt;logger name=&quot;org.hibernate.stat&quot; level=&quot;DEBUG&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-28 오후 2.32.44.png&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OYhgi/btsCG4k47zK/tdFG7RAuwmb4pWquIImIm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OYhgi/btsCG4k47zK/tdFG7RAuwmb4pWquIImIm0/img.png&quot; data-alt=&quot;세션에서 각각 작업에 걸린 시간&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OYhgi/btsCG4k47zK/tdFG7RAuwmb4pWquIImIm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOYhgi%2FbtsCG4k47zK%2FtdFG7RAuwmb4pWquIImIm0%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;1270&quot; height=&quot;280&quot; data-filename=&quot;스크린샷 2023-12-28 오후 2.32.44.png&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;세션에서 각각 작업에 걸린 시간&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;id-하이버네이트성능분석하기-2.슬로우쿼리를찾기&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2. 슬로우 쿼리 찾기&amp;nbsp;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;필요한 경우, 운영환경에 적용해서 모니터링 해보자.&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1703752310478&quot; class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// application.yaml
spring.jpa.properties.hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS=1

// logback.xml
&amp;lt;logger name=&quot;org.hibernate.SQL_SLOW&quot; level=&quot;INFO&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-28 오후 2.40.14.png&quot; data-origin-width=&quot;2432&quot; data-origin-height=&quot;143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O41Pn/btsCDhrtk3C/9sumAgIS4tRSWXdxizJE4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O41Pn/btsCDhrtk3C/9sumAgIS4tRSWXdxizJE4k/img.png&quot; data-alt=&quot;이놈의 티히스토리... 세로로 사진을 늘릴수가 없다...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O41Pn/btsCDhrtk3C/9sumAgIS4tRSWXdxizJE4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO41Pn%2FbtsCDhrtk3C%2F9sumAgIS4tRSWXdxizJE4k%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;860&quot; height=&quot;51&quot; data-filename=&quot;스크린샷 2023-12-28 오후 2.40.14.png&quot; data-origin-width=&quot;2432&quot; data-origin-height=&quot;143&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;2021-08-15 10:54:52.397&amp;nbsp; INFO 31972 --- [&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; main] org.hibernate.SQL_SLOW&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; : SlowQuery: 11 milliseconds. SQL: 'HikariProxyPreparedStatement@1734615070 wrapping select tournament0_.players_id as players_2_4_0_, tournament0_.tournaments_id as tourname1_4_0_, chesstourn1_.id as id1_2_1_, chesstourn1_.end_date as end_date2_2_1_, chesstourn1_.name as name3_2_1_, chesstourn1_.start_date as start_da4_2_1_, chesstourn1_.version as version5_2_1_ from chess_tournament_players tournament0_ inner join chess_tournament chesstourn1_ on tournament0_.tournaments_id=chesstourn1_.id where tournament0_.players_id=1'&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;출처&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;a style=&quot;color: #0593d3;&quot; href=&quot;https://thorben-janssen.com/hibernate-features-with-spring-data-jpa&quot;&gt;https://thorben-janssen.com/hibernate-features-with-spring-data-jpa&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>웹 개발/Spring Framework</category>
      <author>희랍인 조르바</author>
      <guid isPermaLink="true">https://zorba91.tistory.com/366</guid>
      <comments>https://zorba91.tistory.com/366#entry366comment</comments>
      <pubDate>Thu, 28 Dec 2023 17:43:06 +0900</pubDate>
    </item>
    <item>
      <title>[java, kotlin] DecimalFormat은 Thread safe하지 않다.</title>
      <link>https://zorba91.tistory.com/365</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 외부로 api 요청을 하는데, 말도 안되는 값이 들어가서 외부 서비스에서 sql 에러가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LocalDate로 타입을 아예 정해뒀는데, 로그를 확인해보니 들어간 값이 예를 들어, &quot;-E3498230&quot;로 들어간 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응? 타입이 정해져있는데 이런 값 자체가 어떻게 넘어갔지하고 디버깅, 테스트, 구글링해보니 원인은 &lt;b&gt;DecimalFormat&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;u&gt;DecimalFormat(NumberFormat 또한 같다)은 제목에 적힌것처럼 Thread safe하지 않다.&lt;/u&gt; 동시성 이슈는 재연이 어려워서 놓치기 쉽고 발견도 쉽지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행인건 빨리 찾아서 이슈를 해결했다는 것이었다.&lt;/p&gt;
&lt;hr 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;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;당시 이슈가 생긴 코드&lt;/p&gt;
&lt;pre id=&quot;code_1702094070088&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object ExampleFormatUtils

// top-level variable
private val decimalFormat = DecimalFormat()

// 의도: 텍스트에서 숫자만 찾아서 뽑아낸다.
fun String.extractInt(): Int = decimalFormat.parse(this.filter { it.isDigit() }).toInt()&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;나중에 리팩토링했다. 굳이 DecimalFormat이 없어도 숫자만 뽑아낼 수 있다. 예시를 위해서 그때 당시 코드를 그대로 사용한다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun String.extraceInt(): Int = this.filter { it.isDigit() }.toInt()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(리팩토링한 코드)&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;테스트 코드&lt;/p&gt;
&lt;pre id=&quot;code_1702096136973&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import io.kotest.core.spec.style.BehaviorSpec
import kotlinx.coroutines.*

internal class DecimalFormatTest: BehaviorSpec({

    Given(&quot;날짜 개념이 담긴 문자열&quot;) {
        val message = &quot;2024년 02월 23일 부터 2024년 04월 25일까지 입니다.&quot;
        val words = message.split(&quot; &quot;)

        When(&quot;문자열에서 숫자를 뽑아내서 LocalDate로 만든다&quot;) {
            launch {
                (1..10).map {
                    // 스레드 경합이 일어날 수 있도록 코루틴(Dispatchers.IO)를 사용했다.
                    // java에서는 여러 스레드를 만들어서 테스트해볼 수 있다.
                    async(Dispatchers.IO) {
                        val years = words.filter { it.contains(&quot;년&quot;) }.map { it.extractInt() }
                        val months = words.filter { it.contains(&quot;월&quot;) }.map { it.extractInt() }
                        val days = words.filter { it.contains(&quot;일&quot;) }.map { it.extractInt() }

                        println(&quot;startDate year: ${years[0]}, month: ${months[0]}, day: ${days[0]}&quot;)
                        println(&quot;endDate year: ${years[1]}, month: ${months[1]}, day: ${days[1]}&quot;)
                    }
                }.awaitAll()
            }
            Then(&quot;DecimalFormat을 Thread safe하지 않다.&quot;)
        }
    }
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;결과&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-09 오후 1.31.09.png&quot; data-origin-width=&quot;1373&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VutNh/btsBFo5qVSj/KKZL9KoQd5BAK9KS52M0SK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VutNh/btsBFo5qVSj/KKZL9KoQd5BAK9KS52M0SK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VutNh/btsBFo5qVSj/KKZL9KoQd5BAK9KS52M0SK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVutNh%2FbtsBFo5qVSj%2FKKZL9KoQd5BAK9KS52M0SK%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;1373&quot; height=&quot;846&quot; data-filename=&quot;스크린샷 2023-12-09 오후 1.31.09.png&quot; data-origin-width=&quot;1373&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 있다는걸 쉽게 확인할 수 있다.&amp;nbsp; &lt;u&gt;의도대로라면 startDate는 2024년 2월 23일, endDate는 2024년 4월 25일&lt;/u&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;그렇지만 로그를 보면 year 값이 4가 나오고, 2가 나오기도 한다. day에는 2525가 들어가있기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국에는 Int로 변환할 수 없는 값이 나오면서 에러가 발생하고 실행을 멈 춘다.&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 지역변수로 DecimalFormat을 쓰고 있다면 이슈는 생기지 않는다.&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;DecimalFormat 같이 공유해서 쓰기 좋은 클래스는 자원 낭비없이 유틸성으로 빼두는게 좋지 않을까란 생각으로  로컬 변수로 쓰지 않았었다. 그래서 동시성 이슈가 발생했지만 :-(&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예시&lt;/p&gt;
&lt;pre id=&quot;code_1702096847283&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;internal class DecimalFormatTest: BehaviorSpec({

    Given(&quot;날짜 개념이 담긴 문자열&quot;) {
        val message = &quot;2024년 02월 23일 부터 2024년 04월 25일까지 입니다.&quot;
        val words = message.split(&quot; &quot;)

        When(&quot;문자열에서 숫자를 뽑아내서 LocalDate로 만든다&quot;) {
            launch {
                (1..10).map {
                    async(Dispatchers.IO) {
                        // 로컬 변수로 사용한다
                        val decimalFormat = DecimalFormat()

                        val years = words
                            .filter { it.contains(&quot;년&quot;) }
                            .map { word -&amp;gt; decimalFormat.parse(word.filter { it.isDigit() }).toInt() }
                        val months = words
                            .filter { it.contains(&quot;월&quot;) }
                            .map { word -&amp;gt; decimalFormat.parse(word.filter { it.isDigit() }).toInt() }
                        val days = words
                            .filter { it.contains(&quot;일&quot;) }
                            .map { word -&amp;gt; decimalFormat.parse(word.filter { it.isDigit() }).toInt() }

                        println(&quot;startDate year: ${years[0]}, month: ${months[0]}, day: ${days[0]}&quot;)
                        println(&quot;endDate year: ${years[1]}, month: ${months[1]}, day: ${days[1]}&quot;)
                    }
                }.awaitAll()
            }
            Then(&quot;DecimalFormat을 Thread safe하다.&quot;)
        }
    }
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-09 오후 1.46.48.png&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OzgL2/btsBJh4Qm4u/1JkwJkbKLeqEQULW1qBIdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OzgL2/btsBJh4Qm4u/1JkwJkbKLeqEQULW1qBIdK/img.png&quot; data-alt=&quot;원하는 값 그대로 나온다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OzgL2/btsBJh4Qm4u/1JkwJkbKLeqEQULW1qBIdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOzgL2%2FbtsBJh4Qm4u%2F1JkwJkbKLeqEQULW1qBIdK%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;447&quot; height=&quot;444&quot; data-filename=&quot;스크린샷 2023-12-09 오후 1.46.48.png&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;444&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;b&gt;2. 스레드마다 가지고 있도록 DecimalFormat을 스레드 로컬 변수로 만든다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1702097410577&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// 해결한 코드
object ExampleFormatUtils

// top-level variable
private val decimalFormat = ThreadLocal.withInitial { DecimalFormat() }

fun String.extractInt(): Int = decimalFormat.get().parse(this.filter { it.isDigit() }).toInt()&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;i&gt;참고로 get()까지 호출해서 전역 또는 top-level 변수로 두지 않아야한다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;호출하는 쪽에서 get()을 사용해야한다.&lt;/u&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;스크린샷 2023-12-09 오후 1.53.19.png&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVOtOM/btsBFm0NlHO/K9roELaBRmiHkw5KoqTfD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVOtOM/btsBFm0NlHO/K9roELaBRmiHkw5KoqTfD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVOtOM/btsBFm0NlHO/K9roELaBRmiHkw5KoqTfD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVOtOM%2FbtsBFm0NlHO%2FK9roELaBRmiHkw5KoqTfD0%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;526&quot; height=&quot;400&quot; data-filename=&quot;스크린샷 2023-12-09 오후 1.53.19.png&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;400&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;나의 경우, 2번으로 해결했다. 1번에 말한 사용하지 않으려했던 이유로 2번을 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(예시가 된 코드를 리팩토링 했지만, 그외에도 DecimalFormat을 쓰고 있는 부분들이 있었으므로 해결은 해야했다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바는 아래처럼 선언하면 쓸 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1702097796455&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private static ThreadLocal&amp;lt;DecimalFormat&amp;gt; decimalFormat = ThreadLocal.withInitial(() -&amp;gt; new DecimalFormat());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공식 레퍼런스에도 설명이 나와있다.&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-09 오후 2.07.39.png&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;83&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZO9Mo/btsBHsMklA2/YEUOyWfJNjikepk7DkxRtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZO9Mo/btsBHsMklA2/YEUOyWfJNjikepk7DkxRtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZO9Mo/btsBHsMklA2/YEUOyWfJNjikepk7DkxRtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZO9Mo%2FbtsBHsMklA2%2FYEUOyWfJNjikepk7DkxRtk%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;860&quot; height=&quot;47&quot; data-filename=&quot;스크린샷 2023-12-09 오후 2.07.39.png&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;83&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Synchronization&lt;br /&gt;Decimal formats are generally not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #474747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #474747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;a style=&quot;color: #0593d3;&quot; href=&quot;https://docs.oracle.com/javase/8/docs/api/java/text/DecimalFormat.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 레퍼런스&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대략 번역하면, Decimal formats들은 전반적으로 동기화가 안된다. 각 스레드 당 인스턴스를 생성하길 추천한다. 만약 다수의 스레드가 하나의 format에 동시에 접근하면, 동기화는 외부에서 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;번외&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DecimalFormat의 format 함수는 테스트 코드를 돌려봤는데, 동시성 이슈가 발생하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 StringBuffer(혹시 모르는 사람들을 위해: StringBuffer는 Thread safe하다)를 쓰고 있어서 동시성 이슈가 생기지 않는 것으로 추정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 내부구현은 언제든 바뀔 수 있는거니 ThreadLocal이나 지역변수로 미리 처리해두면 나쁠 건 없겠다.&lt;/p&gt;</description>
      <category>프로그래밍 언어/자바 &amp;amp; 코틀린</category>
      <category>DecimalFormat</category>
      <category>Decimalformat concurrency</category>
      <category>decimalformat synchronize</category>
      <category>DecimalFormat thread safe</category>
      <category>decimalformat 동시성</category>
      <category>kotlin</category>
      <category>NumberFormat</category>
      <category>numberformat concurrency</category>
      <category>numberformat thread safe</category>
      <author>희랍인 조르바</author>
      <guid isPermaLink="true">https://zorba91.tistory.com/365</guid>
      <comments>https://zorba91.tistory.com/365#entry365comment</comments>
      <pubDate>Sat, 9 Dec 2023 14:16:06 +0900</pubDate>
    </item>
    <item>
      <title>[트러블 슈팅] Spring 3.1 이상, Hibernate(JPA) 사용시 발생할 수 있는 이슈 및 내부동작 파헤치기</title>
      <link>https://zorba91.tistory.com/363</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;하이버네이트 6.2 마이그레이션 가이드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.jboss.org/hibernate/orm/6.2/migration-guide/migration-guide.html#ddl-changes&quot;&gt;https://docs.jboss.org/hibernate/orm/6.2/migration-guide/migration-guide.html#ddl-changes&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;스프링 부트 3.0 &amp;rarr; 3.1 버전업을 하면서 하이버네이트도 덩달아 6.1 &amp;rarr; 6.2로 업그레이드 되었다.&lt;/u&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;이 영향으로 timezone을 다루는 date 타입이 DB에 저장될 때 의도와는 다른 시간으로 저장될 수 있다.&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;700&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ANRPD/btsAPW8BNT5/598xK33K7OnMq1ytdUfXN1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ANRPD/btsAPW8BNT5/598xK33K7OnMq1ytdUfXN1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ANRPD/btsAPW8BNT5/598xK33K7OnMq1ytdUfXN1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FANRPD%2FbtsAPW8BNT5%2F598xK33K7OnMq1ytdUfXN1%2Fimg.webp&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;700&quot; height=&quot;403&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;403&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;(별 다른 설정이 없으면 하이버네이트에서 시간 변환을 UTC로 강제하기 때문에 DB의 타임존이 UTC로 쓰고 있으면 문제가 없을 수 있다 또는 MySQL 이외에 타임존을 지원해주는 DBMS 사용하면 UTC를 강제하진 않는다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론부터 말하면, application.yaml에서 아래 설정을 해주면 된다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;spring.jpa.properties.hibernate.timezone.default_storage: NORMALIZE&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의도된대로 동작하더라도 기존 쓰던 방식과 호환하려면 하이버네이트 가이드에 맞춰 위의 설정을 해주는게 좋을듯싶다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제상황 예시)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기존 환경&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;MySQL의 DB 타임존은 Asia/Seoul로 설정되어 있음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;애플리케이션 서버(Spring을 사용하는 서버)도 타임존을 Asia/Seoul로 쓰고 있음&lt;/li&gt;
&lt;li&gt;테이블의 컬럼은 타임존을 활용하는 컬럼 타입 사용: TIMESTAMP(참고로 MySQL의 TIMESTAMP는 내부적으로 UTC로 저장해뒀다가 클라이언트 타임존에 맞춰서 시간을 변환해서 반환해줌)
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #555555;&quot;&gt;MySQL converts&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;TIMESTAMP&lt;span style=&quot;color: #555555;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;values from the current time zone to UTC for storage, and back from UTC to the current time zone for retrieval. (This does not occur for other types such as&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;DATETIME&lt;span style=&quot;color: #555555;&quot;&gt;.) By default, the current time zone for each connection is the server's time.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 발생 플로우&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;애플리케이션 코드에서 OffsetDateTime.now(ZoneId.of(&quot;Asia/Seoul&quot;))를 통해 &quot;2023-11-23 10:00:00+09:00&quot; 시간 정보를 획득&lt;/li&gt;
&lt;li&gt;엔티티 필드 변수(보통, created_at이나 updated_at 필드가 있을 듯)에 값을 할당&lt;/li&gt;
&lt;li&gt;해당 엔티티 필드는 MySQL의 TIMESTAMP를 사용&lt;/li&gt;
&lt;li&gt;기대 상황: &quot;2023-11-23 01:00:00Z&quot;로 저장&lt;/li&gt;
&lt;li&gt;실제 결과: &quot;2023-11-22 16:00:00Z&quot;로 저장됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;KST 타임존을 쓰는 MySQL&lt;/b&gt;이라 &lt;u&gt;타임존 정보 없이 시간만 보내주면 한국 시간으로 인식해서 저장해버림&lt;/u&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;하이버네이트가 &quot;2023-11-23 01:00:00&quot;라는 String으로 요청함.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DB에서 쿼리로 select 해보면 클라이언트 타임존이 KST라면,&amp;nbsp; &quot;2023-11-23 01:00:00Z&quot;로 표기되어 헷갈릴 수 있음. &lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;unix_timestamp(타임스탬프 컬럼) 함수를 이용해서 변환해보면 잘못된 시간임을 알 수 있음.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내부 동작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제부터 집중력이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;org.hibernate.annotations.TimeZoneStorageType 클래스에서 DEFAULT를 보면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPawlt/btsAQcpVBW5/ezfmACotTkBzNOZhIo41Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPawlt/btsAQcpVBW5/ezfmACotTkBzNOZhIo41Nk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPawlt/btsAQcpVBW5/ezfmACotTkBzNOZhIo41Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPawlt%2FbtsAQcpVBW5%2FezfmACotTkBzNOZhIo41Nk%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;783&quot; height=&quot;175&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dialect.getTimeZoneSupport에서 NATIVE 값을 쓰지 않으면 자동으로 NORMALIZE_UTC를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL은 없다.(아마 UTC로 통일해서 저장해서 지원하지 않는 것으로 보임) 그래서 &lt;u&gt;자동적으로 하이버네이트는 NORMALIZE_UTC를 선택한다.&lt;/u&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;org.hibernate.boot.internal.MetadataBuilderImpl 클래스의 toTimeZoneStorageStrategy(TimeZoneSupport timeZoneSupport) 메서드에서 defaultTimezoneStorage가 'DEFAULT'면서 timeZoneSupport가 'NONE'이면 'NORMALIZE_UTC를 선택하는걸 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1219&quot; data-origin-height=&quot;889&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3IRDY/btsAPY6oVot/WVmKtpFXFCh8088C2eGbCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3IRDY/btsAPY6oVot/WVmKtpFXFCh8088C2eGbCK/img.png&quot; data-alt=&quot;MetadataBuilderImpl의 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3IRDY/btsAPY6oVot/WVmKtpFXFCh8088C2eGbCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3IRDY%2FbtsAPY6oVot%2FWVmKtpFXFCh8088C2eGbCK%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;1219&quot; height=&quot;889&quot; data-origin-width=&quot;1219&quot; data-origin-height=&quot;889&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MetadataBuilderImpl의 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TimeZoneStorageType이 NORMALIZE_UTC로 선택되면, TIMESTAMP 타입은 org.hibernate.type.SqlTypes 클래스에 있는 TIMESTAMP_UTC라는 상수를 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;org.hibernate.boot.model.process.spi.MetadataBuildingProcess 클래스에서 getTimestampWithTimeZoneOverride() 메서드에서 jdbcType을 TIMESTAMP_UTC를 선택하는걸 볼 수 있다.&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;1282&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCks2I/btsAM4GKto8/lFrWtugzuNIq6umHF2Gyzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCks2I/btsAM4GKto8/lFrWtugzuNIq6umHF2Gyzk/img.png&quot; data-alt=&quot;MetadataBuildingProcess 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCks2I/btsAM4GKto8/lFrWtugzuNIq6umHF2Gyzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCks2I%2FbtsAM4GKto8%2FlFrWtugzuNIq6umHF2Gyzk%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;860&quot; height=&quot;193&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MetadataBuildingProcess 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;TIMESTAMP_UTC를 핸들링하는 클래스&lt;/u&gt;는 org.hibernate.type.descriptor.jdbc.&lt;b&gt;TimestampUtcAsJdbcTimestampJdbcType&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;getBinder() 메서드에서 st.setTimeStamp를 따라가보면,&amp;nbsp;com.zaxxer.hikari.pool.HikariProxyPreparedStatement를 통해 com.mysql.cj.jdbc.ClientPreparedStatement의 setTimeStamp 메서드를 탄다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1247&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkaRp1/btsAMpq0usd/qTo6VEOqJ8CvcCirZ4RLv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkaRp1/btsAMpq0usd/qTo6VEOqJ8CvcCirZ4RLv1/img.png&quot; data-alt=&quot;TimestampUtcAsJdbcTimestampJdbcType의 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkaRp1/btsAMpq0usd/qTo6VEOqJ8CvcCirZ4RLv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkaRp1%2FbtsAMpq0usd%2FqTo6VEOqJ8CvcCirZ4RLv1%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;1247&quot; height=&quot;412&quot; data-origin-width=&quot;1247&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;TimestampUtcAsJdbcTimestampJdbcType의 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jnl0F/btsAON5Dqcy/FijMu7AuMekysFY1cgEPVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jnl0F/btsAON5Dqcy/FijMu7AuMekysFY1cgEPVK/img.png&quot; data-alt=&quot;com.mysql.cj.jdbc.ClientPreparedStatement의 setTimeStamp 타는 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jnl0F/btsAON5Dqcy/FijMu7AuMekysFY1cgEPVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJnl0F%2FbtsAON5Dqcy%2FFijMu7AuMekysFY1cgEPVK%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;1320&quot; height=&quot;164&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;com.mysql.cj.jdbc.ClientPreparedStatement의 setTimeStamp 타는 코드&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;위 코드에서 ((PreparedQuery) this.query).getQueryBidnings().setTimestamp()가 동작하는 곳은 com.mysql.cj.NativeQueryBindings 클래스의 setTimestamp() 메서드이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;527&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Wrrmx/btsAOVvFoLL/Z5RgfpyKCKfbnkDn4hMhn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Wrrmx/btsAOVvFoLL/Z5RgfpyKCKfbnkDn4hMhn0/img.png&quot; data-alt=&quot;NativeQueryBindings의 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wrrmx/btsAOVvFoLL/Z5RgfpyKCKfbnkDn4hMhn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWrrmx%2FbtsAOVvFoLL%2FZ5RgfpyKCKfbnkDn4hMhn0%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;1143&quot; height=&quot;527&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;527&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NativeQueryBindings의 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;binding.setBinding은 com.mysql.cj.NativeQueryBindValue 클래스의 setBinding을 탄다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RWbfX/btsAL3hspAh/xYYHEHPJhKwnHetCWIkS61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RWbfX/btsAL3hspAh/xYYHEHPJhKwnHetCWIkS61/img.png&quot; data-alt=&quot;NativeQueryBindValue의 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RWbfX/btsAL3hspAh/xYYHEHPJhKwnHetCWIkS61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRWbfX%2FbtsAL3hspAh%2FxYYHEHPJhKwnHetCWIkS61%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;1306&quot; height=&quot;559&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NativeQueryBindValue의 코드&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;이때 valueEncoder로 받아오는 클래스가 TIMESTAMP 타입일 경우,&amp;nbsp; com.mysql.cj.protocol.a.&lt;b&gt;SqlTimestampValueEncoder&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;h2 data-ke-size=&quot;size26&quot;&gt;여기서 문제 발생.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;SqlTimestampValueEncoder에서 DB에 넣기 위해 값을 변환하는 과정에서 문제가 발생한다.&lt;/u&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;getString(BindValue binding)을 통해 쿼리문에 들어가는 값을 확인할 수 있다. &lt;u&gt;실제 들어갈 값을 만드는 함수는 같은 클래스 내에 &lt;b&gt;encodeAsBinary&lt;/b&gt; 메서드로 추측된다.&amp;nbsp;&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1283&quot; data-origin-height=&quot;867&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EL8oE/btsANueiOgZ/FCJLxPK0qjUh1lqgCFUbR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EL8oE/btsANueiOgZ/FCJLxPK0qjUh1lqgCFUbR0/img.png&quot; data-alt=&quot;SqlTimestampValueEncoder의 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EL8oE/btsANueiOgZ/FCJLxPK0qjUh1lqgCFUbR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEL8oE%2FbtsANueiOgZ%2FFCJLxPK0qjUh1lqgCFUbR0%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;1283&quot; height=&quot;867&quot; data-origin-width=&quot;1283&quot; data-origin-height=&quot;867&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SqlTimestampValueEncoder의 코드&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;b&gt;TimestampUtcAsJdbcTimestampJdbcType&lt;/b&gt;에서 보이듯이 UTC_CALENDAR를 넣어주었기 때문에 아래의 로직을 타게된다.&lt;/p&gt;
&lt;pre id=&quot;code_1700754301969&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (binding.getCalendar() != null) {
   buf.append(TimeUtil.getSimpleDateFormat(&quot;''yyyy-MM-dd HH:mm:ss&quot;, binding.getCalendar()).format(x));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결하기 위해 넣어줘야했다던 설정(&lt;b&gt;spring.jpa.properties.hibernate.timezone.default_storage: NORMALIZE&lt;/b&gt;)을 넣게되면, TimestampUtcAsJdbcTimestampJdbcType가 아닌&amp;nbsp; org.hibernate.type.descriptor.jdbc.&lt;b&gt;TimestampJdbcType&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;이때 calendar가 null이기 때문에 else 로직을 타게된다.&lt;/p&gt;
&lt;pre id=&quot;code_1700754343820&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;else {
   this.tsdf = TimeUtil.getSimpleDateFormat(this.tsdf, &quot;''yyyy-MM-dd HH:mm:ss&quot;,
       binding.getMysqlType() == MysqlType.TIMESTAMP &amp;amp;&amp;amp; this.preserveInstants.getValue() ? this.serverSession.getSessionTimeZone()
           : this.serverSession.getDefaultTimeZone());
   buf.append(this.tsdf.format(x));
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트 해보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SqlTimestampValueEncoder에서 사용하는 if 로직에서 사용하는 TimeUtil.getSimpleDateFormat의 내부 구현은 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;349&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqh10E/btsANK8962E/mbXpKk0tKBFeNoVNKMpvo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqh10E/btsANK8962E/mbXpKk0tKBFeNoVNKMpvo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqh10E/btsANK8962E/mbXpKk0tKBFeNoVNKMpvo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcqh10E%2FbtsANK8962E%2FmbXpKk0tKBFeNoVNKMpvo1%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;897&quot; height=&quot;349&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;349&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SqlTimestampValueEncoder에서 사용하는 else 로직에서 사용하는 TimeUtil.getSimpleDateFormat의 내부 구현은 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1247&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKga1a/btsAPuLfNmO/BDk5GkHZURQkXqNNHCVp8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKga1a/btsAPuLfNmO/BDk5GkHZURQkXqNNHCVp8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKga1a/btsAPuLfNmO/BDk5GkHZURQkXqNNHCVp8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKga1a%2FbtsAPuLfNmO%2FBDk5GkHZURQkXqNNHCVp8k%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;1247&quot; height=&quot;384&quot; data-origin-width=&quot;1247&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 코드&lt;/h3&gt;
&lt;pre id=&quot;code_1700754468587&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;internal class HibernateMigrationTest: BehaviorSpec({
    Given(&quot;SqlTimestampValueEncoder에서 Timestamp 값으로 변환하는 결과를 확인한다&quot;) {
        val offsetDateTime = OffsetDateTime.now(ZoneId.of(&quot;Asia/Seoul&quot;))
        println(&quot;현재시간: $offsetDateTime&quot;)
 
        // 실제 코드의 변수명
        val x = Timestamp.from(offsetDateTime.toInstant())
 
        When(&quot;캘린더 정보가 존재해서 if 로직을 탔을 경우&quot;) {
            val UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone(&quot;UTC&quot;))
 
            val result = getSimpleDateFormat(
                &quot;''yyyy-MM-dd HH:mm:ss&quot;,
                UTC_CALENDAR
            ).format(x)
 
            Then(&quot;캘린더 정보를 포함해서 시간을 반환한다&quot;) {
                println(&quot;캘린더 정보가 존재해서 if 로직을 탔을 경우, 시간 $result&quot;)
            }
        }
 
        When(&quot;캘린더 정보가 없어서 else 로직을 탔을 경우&quot;) {
            val result = getSimpleDateFormat(
                SimpleDateFormat(&quot;''yyyy-MM-dd HH:mm:ss&quot;),
                &quot;''yyyy-MM-dd HH:mm:ss&quot;,
                TimeZone.getTimeZone(ZoneId.of(&quot;Asia/Seoul&quot;))
            ).format(x)
 
            Then(&quot;입력된 시간 그대로 반환한다&quot;) {
                println(&quot;캘린더 정보가 존재해서 else 로직을 탔을 경우, 시간 $result&quot;)
            }
        }
    }
}) {
    companion object {
        // if 로직
        fun getSimpleDateFormat(pattern: String?, cal: Calendar?): SimpleDateFormat {
            var cal = cal
            val sdf = SimpleDateFormat(pattern, Locale.US)
            if (cal != null) {
                cal = cal!!.clone() as Calendar
                sdf.calendar = cal
            }
            return sdf
        }
 
        // else 로직
        fun getSimpleDateFormat(
            cachedSimpleDateFormat: SimpleDateFormat?,
            pattern: String,
            tz: TimeZone?
        ): SimpleDateFormat {
            val sdf =
                if (cachedSimpleDateFormat != null &amp;amp;&amp;amp; cachedSimpleDateFormat.toPattern() == pattern) cachedSimpleDateFormat else SimpleDateFormat(
                    pattern,
                    Locale.US
                )
            if (tz != null) {
                sdf.timeZone = tz
            }
            return sdf
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;85&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNbItS/btsALJpSXKm/NFTzNw4AAKUyktJJwiUiV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNbItS/btsALJpSXKm/NFTzNw4AAKUyktJJwiUiV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNbItS/btsALJpSXKm/NFTzNw4AAKUyktJJwiUiV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNbItS%2FbtsALJpSXKm%2FNFTzNw4AAKUyktJJwiUiV1%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;780&quot; height=&quot;85&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;85&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;b&gt;KST를 사용하는 DB&lt;/b&gt;에&amp;nbsp;&lt;u&gt; 계속 언급했던 설정을 하지 않고 if 로직을 탈 경우, DB는 '&lt;u&gt;2023-11-23 07:28:56&lt;/u&gt;' 값을 한국 시간으로 취급해 저장해서 시간이 틀어진다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB는 UTC 시간으로 '2023-11-23 01:28:56Z'으로 저장하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>웹 개발/Spring Framework</category>
      <category>hibernate 6.2</category>
      <category>OffsetDateTime</category>
      <category>spring boot 3</category>
      <category>spring boot 3.1</category>
      <category>timestamp</category>
      <category>ZonedDateTime</category>
      <author>희랍인 조르바</author>
      <guid isPermaLink="true">https://zorba91.tistory.com/363</guid>
      <comments>https://zorba91.tistory.com/363#entry363comment</comments>
      <pubDate>Fri, 24 Nov 2023 01:11:31 +0900</pubDate>
    </item>
    <item>
      <title>[백엔드 개발자] 대학교에서 직업 강연을 해보았다.</title>
      <link>https://zorba91.tistory.com/358</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;마동석짤.jpeg&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FDqGE/btsvqA4g30D/Q4Bt8dp7EVAY5evGvtTmrK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FDqGE/btsvqA4g30D/Q4Bt8dp7EVAY5evGvtTmrK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FDqGE/btsvqA4g30D/Q4Bt8dp7EVAY5evGvtTmrK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFDqGE%2FbtsvqA4g30D%2FQ4Bt8dp7EVAY5evGvtTmrK%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;720&quot; height=&quot;405&quot; data-filename=&quot;마동석짤.jpeg&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그로 알게 됐지만 지금은 아주 친한 사이가 된 친구와 술자리 중에 자신의 모교에 직업 강연을 권유받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 개발자 취업을 준비하는 대학생들에게 해당 직무에 대한 주제로 하는 강연이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대상 학교 담당 선생님과 연락을 하게됐는데, 1시간 정도일 줄 알았던 강연 시간이 3시간을 해야한다는 것이었다. 어우.. 발표 제일 많이 하던 대학생 때도 30분 이상 발표를 해본적이 없는데, 3시간을 어떻게 하나 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;쫄았제.jpeg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mchDy/btsvdrajEBi/QkBBAXjfS344dK5bD6eXJk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mchDy/btsvdrajEBi/QkBBAXjfS344dK5bD6eXJk/img.jpg&quot; data-alt=&quot;심히 당황..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mchDy/btsvdrajEBi/QkBBAXjfS344dK5bD6eXJk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmchDy%2FbtsvdrajEBi%2FQkBBAXjfS344dK5bD6eXJk%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;600&quot; height=&quot;338&quot; data-filename=&quot;쫄았제.jpeg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&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;담당 선생님이 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;기왕 하겠다고 마음을 먹었기 때문에 '에잇 어떻게든 되겠지' 마음으로 담당 선생님에게 하겠다고 말씀드렸다.&lt;/p&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시간을 발표해야할 생각하니 아득했다. 주제 선정, 목차 구성, 피피티 작업 등 칼퇴하고 강연 준비에 매달렸다. 1주일 남았을 때는 새벽 늦게까지 준비했는데 ppt 슬라이드가 50장은 나왔다.&lt;/p&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시간 강연하려면 완벽한 스크립트는 불가능에 가까울거라 생각해 슬라이드마다 큰 맥락의 단어들로 작성해서 준비했다.&lt;/p&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10~15명 정도 강의실을 채웠는데, 준비해간 피피티를 띄우려니 설치된 데스크톱으로만 프레젠테이션이 연결돼서 1차 당황했다. 부랴부랴 pdf로 전환해서 데스크톱에 다운받아 발표했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작하고 15분 후까지는 긴장으로 목소리가 떨렸던 것으로 기억한다. 서서히 긴장이 풀리면서 목소리가 떨리는건 멈췄다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 내가 전문 발표자는 아니다보니 말이 중언부언했을 것이다. 왜냐면 강연이 끝난 뒤 내가 했던 말들의 디테일이 하나도 생각나지 않았기 때문이다.&lt;/p&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;3시간을 말하는 것도 힘들었지만, 3시간을 끝까지 경청해준 학생분들에게 고마웠다. 대학생 때 연강 3시간은 버티기 힘들었던걸 회상해본다면.&lt;/p&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;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;강연후기3.jpeg&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnLE4w/btsvHcaxxYI/y9MAxHizQLajrxAMYJWZTk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnLE4w/btsvHcaxxYI/y9MAxHizQLajrxAMYJWZTk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnLE4w/btsvHcaxxYI/y9MAxHizQLajrxAMYJWZTk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnLE4w%2FbtsvHcaxxYI%2Fy9MAxHizQLajrxAMYJWZTk%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;622&quot; height=&quot;1280&quot; data-filename=&quot;강연후기3.jpeg&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;강연후기1.jpeg&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chb4ud/btsvvDGxZgZ/kB7DF2qB2WZ0PZtSloEKQK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chb4ud/btsvvDGxZgZ/kB7DF2qB2WZ0PZtSloEKQK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chb4ud/btsvvDGxZgZ/kB7DF2qB2WZ0PZtSloEKQK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fchb4ud%2FbtsvvDGxZgZ%2FkB7DF2qB2WZ0PZtSloEKQK%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;622&quot; height=&quot;1280&quot; data-filename=&quot;강연후기1.jpeg&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;강연후기2.jpeg&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NMENv/btsvkPnSqB9/sG28OOIfBKnog021Sb224K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NMENv/btsvkPnSqB9/sG28OOIfBKnog021Sb224K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NMENv/btsvkPnSqB9/sG28OOIfBKnog021Sb224K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNMENv%2FbtsvkPnSqB9%2FsG28OOIfBKnog021Sb224K%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;622&quot; height=&quot;1280&quot; data-filename=&quot;강연후기2.jpeg&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개발 일기장/개발 일상</category>
      <category>개발자</category>
      <category>백엔드</category>
      <category>백엔드개발</category>
      <category>직업강연</category>
      <author>희랍인 조르바</author>
      <guid isPermaLink="true">https://zorba91.tistory.com/358</guid>
      <comments>https://zorba91.tistory.com/358#entry358comment</comments>
      <pubDate>Sun, 24 Sep 2023 23:26:49 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Batch] 이제 LocalDate도 지원돼요!</title>
      <link>https://zorba91.tistory.com/356</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;많은 블로그를 보면 스프링 배치에서 LocalDateTime, LocalDate, LocalTime 등이 jobParameters로 지원되지 않아서 String을 파싱하는 방법으로 알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 java 8의 time api를 지원하지 않아서 String을 파싱하는 방법 밖에 없었다. 그 이후로 최신 글이 없다보니 지금의 개발자들도 String으로 파싱해서 사용하고 있지 않을까해서 포스팅 하기.&lt;/p&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;스프링 배치에서 java 8의 time api를 2022년 10월부터 지원하고 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고글 : &lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;a style=&quot;color: #0593d3;&quot; href=&quot;https://github.com/spring-projects/spring-batch/issues/1035&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/spring-projects/spring-batch/issues/1035&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;빵긋.jpeg&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOvxc5/btssOtuBn4C/J3QBI5akymEwkh4xX0AF90/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOvxc5/btssOtuBn4C/J3QBI5akymEwkh4xX0AF90/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOvxc5/btssOtuBn4C/J3QBI5akymEwkh4xX0AF90/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOvxc5%2FbtssOtuBn4C%2FJ3QBI5akymEwkh4xX0AF90%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;598&quot; height=&quot;478&quot; data-filename=&quot;빵긋.jpeg&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;960&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;h4 data-ke-size=&quot;size20&quot;&gt;준비물&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버전: &lt;u&gt;&lt;b&gt;스프링 배치 5.0.0 이상, 스프링 부트 배치 3.0 이상&lt;/b&gt;&lt;/u&gt;(아직까지 스프링 부트 2.x 버전을 많이 사용해서 바로 적용은 힘들지도)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스프링 배치를 돌리기 위한 밑작업(스프링 배치가 사용하는 테이블 생성, db 연동, 같은 jobParameter로 n번 돌리기 위한 incrementer)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;테스트 시작&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고용으로 내가 사용한 &lt;b&gt;커스텀 incrementer&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693637356627&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
class CurrentTimeIncrementer : JobParametersIncrementer {

    override fun getNext(parameters: JobParameters?): JobParameters {
        return JobParametersBuilder()
            .addLong(&quot;currentTimeMillis&quot;, System.currentTimeMillis())
            .toJobParameters()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;application.yaml&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693641845969&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    driverClassName: com.mysql.cj.jdbc.Driver
  batch:
    job:
      name: ${job.name:NONE}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;build.gradle.kts&lt;/b&gt; 추가한 의존성(로컬에 이미 세팅이 되어있는 mysql을 사용했다)&lt;/p&gt;
&lt;pre id=&quot;code_1693641940992&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation(&quot;org.springframework.boot:spring-boot-starter-batch&quot;)
implementation(&quot;org.jetbrains.kotlin:kotlin-reflect&quot;)
runtimeOnly(&quot;com.mysql:mysql-connector-j&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SampleJobConfiguration.kt&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693639366143&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
class SampleJobConfiguration(
    private val transactionManager: PlatformTransactionManager,
    private val jobRepository: JobRepository,
) {

    @Bean
    fun sampleJob(sampleStep: Step): Job = JobBuilder(&quot;sample-job&quot;, jobRepository)
        .incrementer(CurrentTimeIncrementer())
        .flow(sampleStep)
        .end()
        .build()

    @Bean
    @JobScope
    fun sampleStep(
        sampleReader: ItemReader&amp;lt;LocalDateTime&amp;gt;,
        sampleWriter: ItemWriter&amp;lt;LocalDateTime&amp;gt;
    ): Step {

        return StepBuilder(&quot;sampleStep&quot;, jobRepository)
            .chunk&amp;lt;LocalDateTime, LocalDateTime&amp;gt;(10, transactionManager)
            .reader(sampleReader)
            .writer(sampleWriter)
            .build()
    }

    @Bean
    @StepScope
    fun sampleReader(
        // 주목!!
        @DateTimeFormat(pattern = &quot;yyyy-MM-ddHH:mm&quot;)
        @Value(&quot;#{jobParameters[targetDateTime]}&quot;)
        targetDateTime: LocalDateTime,

        @DateTimeFormat(pattern = &quot;yyyy-MM-dd&quot;)
        @Value(&quot;#{jobParameters[targetDate]}&quot;)
        targetDate: LocalDate,

        @DateTimeFormat(pattern = &quot;HH:mm&quot;)
        @Value(&quot;#{jobParameters[targetTime]}&quot;)
        targetTime: LocalTime,
    ): ItemReader&amp;lt;LocalDateTime&amp;gt; {
        return ListItemReader(
            listOf(
                targetDateTime,
                targetDate.atTime(12, 0),
                targetTime.atDate(targetDate)
            )
        )
    }

    @Bean
    @StepScope
    fun sampleWriter(): ItemWriter&amp;lt;LocalDateTime&amp;gt; {
        val itemWriter = ItemWriter&amp;lt;LocalDateTime&amp;gt; {
            println(&quot;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; parameters: ${it.items}&quot;)
        }

        return itemWriter
    }
}&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;u&gt;LocalDateTime은&amp;nbsp; yyyy-MM-dd`T`HH:mm, LocalDate는 yyyy-MM-dd, LocalTime은 HH:mm 패턴을 지키면 DateTimeFormat이 없더라도 알아서 변환된다&lt;/u&gt;. 다른 패턴을 사용하고 싶다면 @DateTimeFormat이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(암묵적으로 패턴을 지키면 변환되더라도 개발자의 실수를 줄이기 위해 @DateTimeFormat을 선언해두고 사용하는게 좋을듯 싶다.)&lt;/p&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_1693639589255&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; ./gradlew bootRun --args='--job.name=sample-job targetDateTime=2023-09-0209:00 targetDate=2023-09-02 targetTime=15:00'&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;인텔리J를 사용하고 있다면, 아래처럼 설정하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-02 오후 4.27.53.png&quot; data-origin-width=&quot;2098&quot; data-origin-height=&quot;1352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boNjsp/btssOsboa8H/IYDvnQUK8NOkRzKVf3Bgv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boNjsp/btssOsboa8H/IYDvnQUK8NOkRzKVf3Bgv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boNjsp/btssOsboa8H/IYDvnQUK8NOkRzKVf3Bgv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboNjsp%2FbtssOsboa8H%2FIYDvnQUK8NOkRzKVf3Bgv1%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;2098&quot; height=&quot;1352&quot; data-filename=&quot;스크린샷 2023-09-02 오후 4.27.53.png&quot; data-origin-width=&quot;2098&quot; data-origin-height=&quot;1352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-02 오후 4.46.36.png&quot; data-origin-width=&quot;2662&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUOcUB/btssNj6LX0N/0jZK71ykfLbWnbIofLEtbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUOcUB/btssNj6LX0N/0jZK71ykfLbWnbIofLEtbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUOcUB/btssNj6LX0N/0jZK71ykfLbWnbIofLEtbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUOcUB%2FbtssNj6LX0N%2F0jZK71ykfLbWnbIofLEtbK%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;2662&quot; height=&quot;372&quot; data-filename=&quot;스크린샷 2023-09-02 오후 4.46.36.png&quot; data-origin-width=&quot;2662&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 변환되었다.&lt;/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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.&amp;nbsp;jobParameters로 넣을 때 띄어쓰기를 사용하면 인식하지 못하기 때문에 값을 붙여써줘야한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693640276202&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@DateTimeFormat(pattern = &quot;yyyy-MM-dd HH:mm&quot;)
@Value(&quot;#{jobParameters[targetDateTime]}&quot;)
targetDateTime: LocalDateTime,&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 수정 후,&amp;nbsp; 아래처럼 커맨드 실행했을 때&lt;/p&gt;
&lt;pre id=&quot;code_1693640322423&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; ./gradlew bootRun --args='--job.name=sample-job targetDateTime=2023-09-02 09:00 targetDate=2023-09-02 targetTime=15:00'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-02 오후 4.36.44.png&quot; data-origin-width=&quot;3432&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNbNeo/btssTpYYlVe/aTgas5eKk8a1vIwy23DMUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNbNeo/btssTpYYlVe/aTgas5eKk8a1vIwy23DMUK/img.png&quot; data-alt=&quot;2023-09-02까지만 인식&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNbNeo/btssTpYYlVe/aTgas5eKk8a1vIwy23DMUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNbNeo%2FbtssTpYYlVe%2FaTgas5eKk8a1vIwy23DMUK%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;3432&quot; height=&quot;588&quot; data-filename=&quot;스크린샷 2023-09-02 오후 4.36.44.png&quot; data-origin-width=&quot;3432&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2023-09-02까지만 인식&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;b&gt;2. 항상 jobParameters의 값을 넣지 않는다면 nullable로 쓰자.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우, 잡이 돌아가는 시간을 기준으로 파라미터를 사용하는데 가끔씩 잡이 실패하거나 잘못 돌아갔을 때, 원하는 파라미터를 넣어서 실행하려고 jobParameters를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Not null로 선언해두면 jobParameters를 입력하지 않으면 non-null 에러가 난다.&lt;/p&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>웹 개발/Spring Framework</category>
      <category>Spring Batch</category>
      <category>spring batch jobparameter time</category>
      <category>spring batch localdate</category>
      <category>spring batch localdatetime</category>
      <category>spring batch localtime</category>
      <author>희랍인 조르바</author>
      <guid isPermaLink="true">https://zorba91.tistory.com/356</guid>
      <comments>https://zorba91.tistory.com/356#entry356comment</comments>
      <pubDate>Sat, 2 Sep 2023 16:50:58 +0900</pubDate>
    </item>
  </channel>
</rss>