<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>DOing</title>
    <link>https://doing7.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 12 Jun 2026 01:39:06 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>mangdo</managingEditor>
    <image>
      <title>DOing</title>
      <url>https://tistory1.daumcdn.net/tistory/4653624/attach/e8aaf34d3fc7452c80b67d6e008d2de5</url>
      <link>https://doing7.tistory.com</link>
    </image>
    <item>
      <title>2년차 개발자의 2022년 회고</title>
      <link>https://doing7.tistory.com/168</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;2021년 회고 보러가기&lt;br&gt;&lt;a href=&quot;https://doing7.tistory.com/152&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://doing7.tistory.com/152&lt;/span&gt;&lt;/a&gt;&lt;br&gt; &lt;br&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. LINER 일주년&lt;/b&gt;&lt;/h2&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkAecK/btrU44gKBcQ/JHO0612RX944OCgHvCAk9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkAecK/btrU44gKBcQ/JHO0612RX944OCgHvCAk9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkAecK/btrU44gKBcQ/JHO0612RX944OCgHvCAk9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkAecK%2FbtrU44gKBcQ%2FJHO0612RX944OCgHvCAk9k%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;355&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; 첫 입사날에 설레어했던 것이 엊그제같은데 어느새 1년 3개월이라는 시간이 지나게되었다. 그 시간동안 LINER에서 잊을 수 없는, 재미있는 시간들을 보냈다. 워낙 배운게 많아서 조금 정리해보려고 한다.&lt;br&gt; &lt;br&gt;&lt;b&gt;# 어떻게 지식을 빠르게 습득하고 적용해야하는가를 배웠다.&lt;/b&gt;&lt;br&gt; Java-Spring-MySQL-AWS 정도밖에 모르던 내가 Kotlin-Ktor-ElasticSearch-MongoDB-Bigquery-Pub/Sub-GCP 기술적으로 여러 경험을 해볼 수 있었다. 뿐만아니라 FCM 푸시, 이메일, Stripe 결제, iOS 인앱 결제같은 기능들을 처음 구현해보고 배울 수 있었다. 기술 자체들에 대해서도 많이 배웠지만, 사실 가장 크게 배운 것은 새로운 지식을 어떻게 빠르게 습득하고 적용해야하는가라고 생각한다.&lt;br&gt; &lt;br&gt;&lt;b&gt;# 현업에서의 장애에 대해서 배웠다.&lt;/b&gt;&lt;br&gt; 현업에서는 어떤식으로 코드들이 동작하고, 실제 유저들이 사용하게 되면 어떤 장애들이 발생하고, 이를 어떻게 고쳐가야하는지, 이를 예방하기 위해서는 어떻게 해야하는지, 또 어떻게 성능을 튜닝해가야하는지를 배울 수 있었다. 덧붙여서 에러, 장애 모니터링하는 방법도 배울 수 있었다.&lt;br&gt; &lt;br&gt;&lt;b&gt;# 다른 백엔드 개발자들과 협업하는 방법에 대해서 배웠다.&lt;/b&gt;&lt;br&gt; LINER에는 총 3명의 백엔드 엔지니어가 있었다. 백엔드 개발자 사이에서 일을 어떻게 분담하고, 어떻게 서로 싱크를 맞춰가야하는지, 배움 공유는 어떻게 해야하는지 배울 수 있었다. 또 코드리뷰와 페어 프로그래밍문화를 경험해보았다.&lt;br&gt; &lt;br&gt;&lt;b&gt;# “스타트업”에 대해서 배울 수 있었다.&lt;/b&gt;&lt;br&gt; 스타트업의 도전 정신, 빠른 실행력, 피드백 문화를 경험해 볼 수 있었다. 스타트업의 빠른 실행력을 뒷받침해야 할 백엔드 개발자로써 자주 변화하고, 완벽보다 유연성이 있는 기획에 대해 어떻게 대처해야할 지도 배울 수 있었다. 또 내가 있었던 LINER에서는 일년동안 기능 조직 → 목적 조직 → 기능 조직으로 변화했었는데, 기능 조직과 목적 조직에서의 일하는 방식과 장단점도 배울 수 있었다.&lt;br&gt; &lt;br&gt;&lt;b&gt;# 하나의 프로덕트를 만드는 메이커로써도 배울 수 있었다.&lt;/b&gt;&lt;br&gt; 백엔드 기술적인 것들 뿐만아니라 하나의 프로덕트를 만드는 메이커로써도 많이 배울 수 있었다. 한 프로젝트의 유일한 백엔드 엔지니어로 들어가서 프론트엔드 개발자, 디자이너, 데이터 분석가, 기획자(PO)와 긴밀하게 일하며 “최소 구현기능 스코프 정의-일정 산정-개발 킥오프-API 설계-DB 설계-구현-데이터 분석을 위한 이벤트 로깅-QA-배포-장애대응-회고” 전반적인 과정에 참여했다. 또 이번년도 4분기에는 백로그 아이디어 제안, 마케팅 컨텐츠 기획(UXW)과 프로젝트 플래너로써 프로젝트를 이끌면서 목표 지표와 롤백 지표 설정, 전반적인 일정 관리, third party API 필요 예산 계산, 관련 법률 확인등 더 다양한 일을 하기도 했다.&lt;br&gt; &lt;br&gt;&lt;b&gt;# 사회생활을 처음 시작한 회사로써 배운 것도 많았다.&lt;/b&gt;&lt;br&gt; 현재 나의 문제 상황을 설명하면서 도움을 요청하는 방법, 상대방을 존중해주면서도 나의 생각을 이야기하는 방법, 인사말의 중요성, 좋은 피드백을 주고 요청하는 방법까지 일 잘하시는 팀원들을 보면서 많이 배울 수 있었다.&lt;br&gt; &lt;br&gt; 마지막으론 LINER 생활 자체도 잊을 수 없는 추억이 될 것 같다. 만우절 기념 교복입고 했던 라이너 데이, 시리즈 B 투자 유치 기념으로 크게 열었던 그랜드 라이너 데이, 생일 파티 등 함께 해서 의미있었고 재밌었던 순간들이 많았다. 일적으로도 인간적으로도 배울 것이 많던 팀원들과 함께해서 즐거웠다. 솔직히 올해 초중순에는 힘들었던 순간도 많았었지만, 함께 열심히해갔던 팀원들 덕분에 “즐거웠다”로 회상할 수 있는 것 같다.&lt;/p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uToZ4/btrVeVCysku/WKdEaMqVqIw5khIbB6vW6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uToZ4/btrVeVCysku/WKdEaMqVqIw5khIbB6vW6k/img.png&quot; data-origin-width=&quot;1798&quot; data-origin-height=&quot;1232&quot; style=&quot;width: 48.9174%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uToZ4/btrVeVCysku/WKdEaMqVqIw5khIbB6vW6k/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuToZ4%2FbtrVeVCysku%2FWKdEaMqVqIw5khIbB6vW6k%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;1798&quot; height=&quot;1232&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DI5Uf/btrVcl2P1gE/4D05Vn1LP3KU5t0XDrQIc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DI5Uf/btrVcl2P1gE/4D05Vn1LP3KU5t0XDrQIc0/img.png&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;1030&quot; style=&quot;width: 49.9198%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DI5Uf/btrVcl2P1gE/4D05Vn1LP3KU5t0XDrQIc0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDI5Uf%2FbtrVcl2P1gE%2F4D05Vn1LP3KU5t0XDrQIc0%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;1534&quot; height=&quot;1030&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1-2. 엔지니어링 세미나&lt;/b&gt;&lt;/h2&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1762&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YkBF8/btrU7Hr3UgE/HJT6NeWOxZcSzswXBSGtc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YkBF8/btrU7Hr3UgE/HJT6NeWOxZcSzswXBSGtc0/img.png&quot; data-alt=&quot; 세미나 발표 &amp;amp;amp;amp;amp;amp; 팀원분들의 감사 슬랙 메시지  &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YkBF8/btrU7Hr3UgE/HJT6NeWOxZcSzswXBSGtc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYkBF8%2FbtrU7Hr3UgE%2FHJT6NeWOxZcSzswXBSGtc0%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;438&quot; height=&quot;231&quot; data-origin-width=&quot;1762&quot; data-origin-height=&quot;930&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; 세미나 발표 &amp;amp;amp;amp;amp; 팀원분들의 감사 슬랙 메시지  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; 일년동안 LINER에서 백엔드개발자로 일하면서 내가 생각하는 4번의 실패(까진 아니지만 후회되는) 사례와 얻어걸린 한번의 성공 사례를 공유하며, “빠르게 변화하는 스타트업에서 백엔드 개발자로 살아남기”로 발표를 했었다. 발표 내용은 나중에 블로그 글에도 정리해서 올릴예정이지만! 요약하면 다음과 같다.&lt;br&gt; 사진에서 보이는 것과 같이 발표는 일년전 입사 면접 질문이였던 “어떤 개발자가 되고 싶으신가요?”에서 부터 시작되었다. 내 일년전 대답은 “남들에게 욕먹지 않는 좋은 코드를 짜는 개발자”였다.&lt;br&gt; 나는 내가 맡은 기능을 구현하는데 있어서 완벽하게 이해하고 설계하고 개발하고 싶었다. 또 당시에는 그렇다고 생각했지만, 뒤돌아서서 생각해보면 아니였고 애초에 불가능하다는 것을 느끼게 되었다.&lt;br&gt; &quot;프로덕트의 미래는 아무도 모르고 아무도 단언할 수 없는 것 같습니다. 해당 기능을 만들었던 나도, 해당 기능을 기획했던 기획자도, LINER에 있는 그 누구도, 이 기능이 앞으로 어떻게 쓰이고 발전할 것인가는 알 수 없는 것 같다고 생각합니다. 그래서 지금 상황에서 선택할 수 있는 가장 적정한 아키텍처, 솔루션을 선택하고, 그뒤에 고쳐나가는 것을 두려워하지말아야하는구나. 이것이 제가 라이너에서 있는 일년동안 가장 고민했었고, 느꼈던 부분이였습니다.&quot; - 발표 중 일부분&lt;br&gt; &lt;br&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 첫 휴가, 나홀로 여행&lt;/b&gt;&lt;/h2&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkcbF9/btrVf7CwJtL/Zpgi49ykK23yv6jppXDAWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkcbF9/btrVf7CwJtL/Zpgi49ykK23yv6jppXDAWK/img.png&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;1328&quot; style=&quot;width: 26.3282%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkcbF9/btrVf7CwJtL/Zpgi49ykK23yv6jppXDAWK/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkcbF9%2FbtrVf7CwJtL%2FZpgi49ykK23yv6jppXDAWK%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;978&quot; height=&quot;1328&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xg30S/btrU6YgjucP/kKEbwKCW4qUF9s27uYLrz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xg30S/btrU6YgjucP/kKEbwKCW4qUF9s27uYLrz0/img.png&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;1344&quot; style=&quot;width: 35.5376%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xg30S/btrU6YgjucP/kKEbwKCW4qUF9s27uYLrz0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXg30S%2FbtrU6YgjucP%2FkKEbwKCW4qUF9s27uYLrz0%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;1336&quot; height=&quot;1344&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhsCWu/btrU8EPqxFr/yhe2FiNOQj8MlYoViANHU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhsCWu/btrU8EPqxFr/yhe2FiNOQj8MlYoViANHU0/img.png&quot; data-origin-width=&quot;1228&quot; data-origin-height=&quot;1226&quot; style=&quot;width: 35.8087%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhsCWu/btrU8EPqxFr/yhe2FiNOQj8MlYoViANHU0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhsCWu%2FbtrU8EPqxFr%2Fyhe2FiNOQj8MlYoViANHU0%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;1228&quot; height=&quot;1226&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;첫 휴가, 나홀로 여행 (in 강릉)&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; 20살 때부터 막연한 로망중에 &quot;혼자서 여행 해보기&quot;가 있었는데, 이번 기회에 해보게 되었다. 아무래도 혼자 여행을 하다보니 온전히 나한테 집중할 수 있다는 것이 가장 큰 매력인 것 같다. 주변 친구 중에는 액티비티를 좋아하는 사람이 없어서 도전을 못해보고 있었던, 서핑에도 도전해봤다. 심지어 나름 잘했다. 이날 하루종일 서핑했었는데 오랜만에 물에 들어간지라 너무 신나게 했었다.&lt;br&gt; 강릉에서도 시외쪽을 가서, 버스 간격이 길고 막차시간도 6시인 적도 있었다. 그래서 버스 정류장에 앉아서 무작정 한시간씩 기다리기도 했고, 막차를 놓쳐서 시내까지 한시간 반이 넘는 시간 동안 걸어가기도 했다. 근데 이 순간들조차도 강릉스럽다고 생각해서 여행온 것 같고 기분 좋았다. 숙소를 일부로 책이 있는 숙소로 잡았었는데, 하루 일과가 끝나고 잠깐씩 차분히 책을 읽는 것 조차 좋았다.&lt;br&gt; 혼자 여행 가서 심심하지 않을까 고민했었던 것도 사실이다. 하지만 아무래도 혼자 여행오다보니 오히려 주변에서 말을 걸어주시는 경우가 많았다. 옆 방 숙소 쓰시는 분, 서핑 수업을 같이 들었던 분, 우연히 들렸던 기념품 가게 사장님등등 사진도 찍어주시고 밥도 같이 먹으며 다양한 사람들을 만날 수 있었고 덕분에 즐거운 여행이 될 수 있었던 것 같다.&lt;br&gt;&lt;br&gt; &lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 좋아하는 사람들과의 여행&lt;/b&gt;&lt;/h2&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIURRk/btrVf8IcKK7/RCk8rwZwJikydphzOTSKkk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIURRk/btrVf8IcKK7/RCk8rwZwJikydphzOTSKkk/img.jpg&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;2992&quot; style=&quot;width: 49.4186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIURRk/btrVf8IcKK7/RCk8rwZwJikydphzOTSKkk/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIURRk%2FbtrVf8IcKK7%2FRCk8rwZwJikydphzOTSKkk%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;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LE5ju/btrVaR1W8Lm/XGIoRTKx8vKritRQgkiukk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LE5ju/btrVaR1W8Lm/XGIoRTKx8vKritRQgkiukk/img.jpg&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;2992&quot; style=&quot;width: 49.4186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LE5ju/btrVaR1W8Lm/XGIoRTKx8vKritRQgkiukk/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLE5ju%2FbtrVaR1W8Lm%2FXGIoRTKx8vKritRQgkiukk%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;/&gt;&lt;/span&gt;&lt;/div&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; 코로나 핑계로 미뤄왔었던 가족여행과 친구와의 여행을 다녀왔다. 나에게 있어 소중한 사람들과 오랜만에 많이 이야기를 하면서 같이 시간을 보낼 수 있어서 좋았다.&lt;br&gt; &lt;br&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 새로운 경험들&lt;/b&gt;&lt;/h2&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;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C8BJE/btrVcmOhjPY/JVXuWAvgc9g66kVkgdhJf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C8BJE/btrVcmOhjPY/JVXuWAvgc9g66kVkgdhJf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C8BJE/btrVcmOhjPY/JVXuWAvgc9g66kVkgdhJf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC8BJE%2FbtrVcmOhjPY%2FJVXuWAvgc9g66kVkgdhJf1%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;323&quot; height=&quot;327&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; 원래부터 빵을 너무 좋아했는데, 단순히 먹는 것을 넘어서 원데이 클래스 수업으로 베이킹을 배우게 되었다. 회사 사람들에게 만들어서 하나씩 드렸었는데 다들 맛있다고 해주셔서 뿌듯하고 기분 좋았었던 경험이였다^ㅡ^ 만드는 자체도 재밌지만, 주변사람들한테 나눠줄 때가 제일 즐거웠던 것 같다. 앞으로 시간날때마다 도전해봐야겠다.&lt;br&gt; &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;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2K8Jo/btrU6YN9Ic7/CZFMr8GQMzSfP1KFV8FY51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2K8Jo/btrU6YN9Ic7/CZFMr8GQMzSfP1KFV8FY51/img.png&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;658&quot; style=&quot;width: 60.2788%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2K8Jo/btrU6YN9Ic7/CZFMr8GQMzSfP1KFV8FY51/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2K8Jo%2FbtrU6YN9Ic7%2FCZFMr8GQMzSfP1KFV8FY51%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;1428&quot; height=&quot;658&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfpXu4/btrU9eQGxSU/2uIjp5qcFQqqIDEghb0Y70/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfpXu4/btrU9eQGxSU/2uIjp5qcFQqqIDEghb0Y70/img.jpg&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;577&quot; style=&quot;width: 38.5584%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfpXu4/btrU9eQGxSU/2uIjp5qcFQqqIDEghb0Y70/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfpXu4%2FbtrU9eQGxSU%2F2uIjp5qcFQqqIDEghb0Y70%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;801&quot; height=&quot;577&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;초지는 과천역에서 14전ㅜ (대충 40분뒤 지하철 도착 예정)&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; 올해부터 과천으로 이사오게 되어 경기도민이 되었다. 동시에 도민의 험난한 출퇴근이 시작되었다. 심지어 4호선밖에 오지 않는 과천역에 사는 나는 (시위+파업)의 여파로 40분이면 갈 거리를 거의 2시간에 걸쳐 출근한적도 있었다. 이젠 나름대로 지하철을 버리고 버스정류장으로 뛰가야하는 순간에 대한 직감과 20분정도의 연착은 당연하게 생각하는 여유로운 도민의 마인드를 가지게 되었다.&lt;br&gt; &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;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;망함 주륵..&lt;br&gt; &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;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwL8bQ/btrU43CdWJR/dI2M299Byv8YKyfpZdzIkK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwL8bQ/btrU43CdWJR/dI2M299Byv8YKyfpZdzIkK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwL8bQ/btrU43CdWJR/dI2M299Byv8YKyfpZdzIkK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwL8bQ%2FbtrU43CdWJR%2FdI2M299Byv8YKyfpZdzIkK%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;313&quot; height=&quot;235&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; 팀원분들과 함께 그룹 필라테스 레슨을 11개월 정도 다녔다. 확실히 같이 하는 사람들이 있으니 오랫동안 나갈 수 있었던 것 같다. 다만 레슨 시간이 5시다보니 일하다가 못가는 경우가 많았다. 또 필라테스가 자세 교정으로써는 좋았지만 운동으로써는 잘 모르겠다는 생각이 들었다.&lt;br&gt; &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;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1092&quot; data-origin-height=&quot;1096&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8ZHzF/btrU37ShEAH/pC8LiG8im11xCFeTtSj2a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8ZHzF/btrU37ShEAH/pC8LiG8im11xCFeTtSj2a1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8ZHzF/btrU37ShEAH/pC8LiG8im11xCFeTtSj2a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8ZHzF%2FbtrU37ShEAH%2FpC8LiG8im11xCFeTtSj2a1%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;249&quot; height=&quot;250&quot; data-origin-width=&quot;1092&quot; data-origin-height=&quot;1096&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; 주말에는 매일, 평일에는 아주 가끔씩 아침 운동으로 러닝을 하고 출근했다. 출근이 11시다보니 조금만 빠르게 움직이면 운동을 하고 갈 수 있었다. 필라테스랑은 다르게 러닝이 더 활동적으로 움직이고, 잡생각이 날라가면서 그자체로 스트레스도 풀리는 효과가 있어서 요즘 시작하게 되었다. 개인적으로는 필테보단 잘 맞는 느낌?!&lt;br&gt; &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;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDEuTd/btrVfjpF7QX/5e39fWWkEWmAsk0efMBNE0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDEuTd/btrVfjpF7QX/5e39fWWkEWmAsk0efMBNE0/img.jpg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; style=&quot;width: 49.601%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDEuTd/btrVfjpF7QX/5e39fWWkEWmAsk0efMBNE0/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDEuTd%2FbtrVfjpF7QX%2F5e39fWWkEWmAsk0efMBNE0%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;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wsNVb/btrVfrHZnDo/AE8p8pfoHUisrggEZvfUFk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wsNVb/btrVfrHZnDo/AE8p8pfoHUisrggEZvfUFk/img.jpg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1088&quot; style=&quot;width: 49.2362%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wsNVb/btrVfrHZnDo/AE8p8pfoHUisrggEZvfUFk/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwsNVb%2FbtrVfrHZnDo%2FAE8p8pfoHUisrggEZvfUFk%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;1080&quot; height=&quot;1088&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; 한식만 주로 먹었었는데, 회사에서 이슬람교인 팀원분이랑 같이 점심을 먹으면서 음식 스코프가 넓어졌다. 우선 절대 돈주고 사먹을 수 없다.. 라고 생각했었던 샐로드와 포케를 먹게되었다, 더불어서 여러 할랄 음식도 먹어보게 되었다. 생각보다 모두 괜찮아서 앞으로도 자주 먹을 것 같다.&lt;br&gt; &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;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o1cEE/btrVfkhQyT0/bkbEvaGQH5nDbig3iN0Tv1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o1cEE/btrVfkhQyT0/bkbEvaGQH5nDbig3iN0Tv1/img.jpg&quot; data-origin-width=&quot;2902&quot; data-origin-height=&quot;3869&quot; style=&quot;width: 26.6401%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o1cEE/btrVfkhQyT0/bkbEvaGQH5nDbig3iN0Tv1/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo1cEE%2FbtrVfkhQyT0%2FbkbEvaGQH5nDbig3iN0Tv1%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;2902&quot; height=&quot;3869&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be9l4q/btrU44HTa1e/kctgbLj4sq5KpaG8qpv1R0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be9l4q/btrU44HTa1e/kctgbLj4sq5KpaG8qpv1R0/img.jpg&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;1078&quot; style=&quot;width: 35.5171%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be9l4q/btrU44HTa1e/kctgbLj4sq5KpaG8qpv1R0/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe9l4q%2FbtrU44HTa1e%2FkctgbLj4sq5KpaG8qpv1R0%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;1078&quot; height=&quot;1078&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NrT8P/btrVamOuSsz/fLUerO5FK67hYHUpKK0e50/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NrT8P/btrVamOuSsz/fLUerO5FK67hYHUpKK0e50/img.jpg&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;1078&quot; style=&quot;width: 35.5171%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NrT8P/btrVamOuSsz/fLUerO5FK67hYHUpKK0e50/img.jpg&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNrT8P%2FbtrVamOuSsz%2FfLUerO5FK67hYHUpKK0e50%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;1078&quot; height=&quot;1078&quot;/&gt;&lt;/span&gt;&lt;/div&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; 평소 가성비있게 취해야한다고 소주를 고집했었지만, 더이상 가성비를 따지지 않아도 괜찮아진 직장인으로써 여러 술을 경험해 보았다. 특히 와인은 다양한 종류로 마셔봤었다. 회사에서 돔페리뇽같은 비싼 와인도 마셔봤지만, 솔직히 가격에 따라서 차이는 못느끼겠고 굳이 고르자면 포트 와인보다는 화이트 와인을, 화이트 와인보다는 레드 와인이 잘 맞았다. 막걸리랑 양주랑 사케도 여러번 시도해봤었는데, 사케가 제일 맛있었다. 무엇보다 잔이 너무 예쁨. 역시 술은 잔이지&lt;br&gt; &lt;br&gt; &lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 기록화&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot; style=&quot;text-align: left;&quot;&gt;&lt;b&gt;# 블로그 &amp;amp; 문서화 (in 회사)&lt;/b&gt;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;944&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dc5skI/btrU6X2J6uQ/f7k1BkYoa4OAE5kxKSrtg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dc5skI/btrU6X2J6uQ/f7k1BkYoa4OAE5kxKSrtg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dc5skI/btrU6X2J6uQ/f7k1BkYoa4OAE5kxKSrtg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdc5skI%2FbtrU6X2J6uQ%2Ff7k1BkYoa4OAE5kxKSrtg1%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;390&quot; height=&quot;195&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;944&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; 내가 나보려고 만들었던 블로그가 이젠 누적방문자수 20만명을 돌파했다. 가끔씩 감사하다는 댓글이 올라오면 기분이 좋아지곤 한다. 사내에서도 문서화를 많이 담당했었고, 문서화라고 하면 나를 먼저 떠올려주시기도 했다. 회사에서 문서를 자주 썼던 이유는 사실 진짜 글을 좋아해서나, 해야한다라는 생각으로 한 것은 아니였다. 단순히 내가 처음 회사에 들어왔을 때 적응하기가 힘들어서, 내 뒷사람은 나보다 덜 힘들길 바라는 마음에 하나둘씩 문서로 남기기 시작했었다. 시작은 DB 문서화 API 문서화였고, 이후에는 백엔드 설계 디자인 문서, 스터디 문서화등등으로 이어졌다.&lt;br&gt; &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;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;2992&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yhf2p/btrU8FOjvSb/TgeMcdj6UtXoQ8QCD4yFt0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yhf2p/btrU8FOjvSb/TgeMcdj6UtXoQ8QCD4yFt0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yhf2p/btrU8FOjvSb/TgeMcdj6UtXoQ8QCD4yFt0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyhf2p%2FbtrU8FOjvSb%2FTgeMcdj6UtXoQ8QCD4yFt0%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;281&quot; height=&quot;281&quot; data-origin-width=&quot;2992&quot; data-origin-height=&quot;2992&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; 새로 생긴 취미인데 꽤 재미있어서 요즘에도 가끔하고 있다. 일상을 올리는 용도로 네이버 블로그 블챌도 참여했었다ㅋㅋㅋ&lt;br&gt; &lt;br&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 코로나&lt;/b&gt;&lt;/h2&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXqG5N/btrU38w273a/ebBj1AcsSMals2k7KzdJhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXqG5N/btrU38w273a/ebBj1AcsSMals2k7KzdJhk/img.png&quot; data-alt=&quot; 목소리도 안나오고 방에서도 나올 수 없어서 카톡으로 외치는중.. &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXqG5N/btrU38w273a/ebBj1AcsSMals2k7KzdJhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXqG5N%2FbtrU38w273a%2FebBj1AcsSMals2k7KzdJhk%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;360&quot; height=&quot;224&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;668&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; 나는 안걸릴 줄 알았다. 코로나는 사실상 이미 다 끝났고 나는 전염병에서 살아남은 슈퍼 DNA라고 생각했었다. 역시 인생은 끝날때까지 끝난게 아니다. 덕분에 진정한 솔로 크리스마스를 보냈다. 무려 10일전부터 예매한 아바타 영화도 포기해야만 했다ㅜㅜ 한참 아팠을때는 정신이 혼미할 정도로 열이 났었고, 꽤 오랫동안 목소리가 안나왔었다.&lt;br&gt; &lt;br&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. 그리고 퇴사&lt;/b&gt;&lt;/h2&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;1461&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7hN9p/btrU8Eonckz/mWG2CrcjnkGimCZQCu9XzK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7hN9p/btrU8Eonckz/mWG2CrcjnkGimCZQCu9XzK/img.jpg&quot; data-alt=&quot; 회사에서 받은 케이크!! 너무 감사합니다  &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7hN9p/btrU8Eonckz/mWG2CrcjnkGimCZQCu9XzK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7hN9p%2FbtrU8Eonckz%2FmWG2CrcjnkGimCZQCu9XzK%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;335&quot; height=&quot;336&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;1461&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; 12월 30일을 마지막으로 1년 3개월의 LINER 생활을 마무리짓게 되었다.(서류상으론 1월 중순 퇴사!) 결정하기 전까지 나에 대해 많은 고민을 했었고 감사하게도 주변 많은 분들이 조언도 해주셨었다. 고민끝에 새로운 도전을 하기위해 퇴사를 결심하게 되었다. 서투르고 부족함이 많았던 나를 항상 예쁘게 봐주시고 가르쳐주셔서 진심으로 감사했고, LINER에서 일할 수 있어서 즐거웠다:)&lt;/p&gt;</description>
      <category>디버깅중  </category>
      <author>mangdo</author>
      <guid isPermaLink="true">https://doing7.tistory.com/168</guid>
      <comments>https://doing7.tistory.com/168#entry168comment</comments>
      <pubDate>Mon, 2 Jan 2023 18:34:30 +0900</pubDate>
    </item>
    <item>
      <title>RDB에서 무중단 스키마 변경 툴 도입기 (Online DDL) -1</title>
      <link>https://doing7.tistory.com/166</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &quot;엄격한 스키마&quot;, RDB의 장점이자 단점으로 꼽히는 특징이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 이 특징으로 인해 안정적으로 서비스를 할 수 있다는 장점을 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 하지만 한 분기에도 수십개의 기능을 만들어내고 또 그 중 대부분을 다시 롤백해야하는 스타트업에서는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스의 스키마를 변경할때마다, 트래픽을 제일 적은 시간에 수행해야하고, 그럼에도 일정 시간동안 장애를 감내야하는 상황은 크리티컬하게 다가왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 모든 스타트업이 그렇다는 것은 아니지만, 적어도 내가 현재 재직하고 있는 회사에서는 그랬었다. 이러한 문제 상황을 해결하고자 데이터 마이그레이션을 도와줄 수 있는 툴을 조사했었다. Prod과 비슷한 테스트 환경을 만들어 모의 테스트를 하고, 이를 바탕으로 다른 백엔드 엔지니어들에게 공유하고, 설득하여 도입했었다. 현재는 &lt;span&gt;무사히 도입에 성공하여,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;실제 서비스 중인 75,000,000 rows 이상의 테이블에 대하여 DDL을 서비스 중단이나 장애없이 수행하고 있다. 이번 포스팅에서는 이를 바탕으로 Online-DDL 도입기에 대해서 이야기해보려고 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;[ Online DDL의 원리, 알고리즘 ]&lt;/span&gt;&lt;/b&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; Instant &amp;gt; InPlace &amp;gt; Copy 순으로 효율적이며 따라서 Mysql 8에서는 Instant가 default로 설정되어 있으나 해당 알고리즘 사용 부분이 매우 제한적이다. Instant가 만약 적용이 안된다면 InPlace, Copy 순으로 적용한다. [1]&lt;span data-token-index=&quot;0&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;1. Copy&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-01-11 오후 8.22.19.png&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;1014&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNzbzH/btrVWy9gQKW/mA8uVpwxehfe9dIOWk37B1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNzbzH/btrVWy9gQKW/mA8uVpwxehfe9dIOWk37B1/img.png&quot; data-alt=&quot;https://mydbops.wordpress.com/2020/03/04/an-overview-of-ddl-algorithms-in-mysql-covers-mysql-8/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNzbzH/btrVWy9gQKW/mA8uVpwxehfe9dIOWk37B1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNzbzH%2FbtrVWy9gQKW%2FmA8uVpwxehfe9dIOWk37B1%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;448&quot; height=&quot;313&quot; data-filename=&quot;스크린샷 2023-01-11 오후 8.22.19.png&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;1014&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://mydbops.wordpress.com/2020/03/04/an-overview-of-ddl-algorithms-in-mysql-covers-mysql-8/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;임시 테이블을 생성하여 DDL을 반영하고, 기존 테이블의 데이터를 복사한 후 기존 테이블과 이름을 변경&lt;/b&gt;하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ALTER TABLE 작업 동안 &lt;span&gt;SHARED&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;LOCK&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Select는 가능하지만, &lt;b&gt;&lt;u&gt;DML(inserts,updates,delete)은 차단&lt;/u&gt;&lt;/b&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;span data-token-index=&quot;0&quot;&gt;inplace&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;스크린샷 2023-01-11 오후 8.55.00.png&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J33KP/btrVZWO2jmS/HIYSYk8bsMICIF8D2lz4sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J33KP/btrVZWO2jmS/HIYSYk8bsMICIF8D2lz4sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J33KP/btrVZWO2jmS/HIYSYk8bsMICIF8D2lz4sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ33KP%2FbtrVZWO2jmS%2FHIYSYk8bsMICIF8D2lz4sk%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;388&quot; height=&quot;274&quot; data-filename=&quot;스크린샷 2023-01-11 오후 8.55.00.png&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;902&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;기존 테이블에 변경 내용을 직접 반영&lt;/b&gt;하여 임시테이블이나 테이블 재생성을 피하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ALTER TABLE&amp;nbsp;중 들어오는 &lt;u&gt;&lt;b&gt;DML 쿼리는 Online Alter Log buffer에 쌓이고 테이블 변경이 완료 후 반영&lt;/b&gt;&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;1. 너무 긴 DDL 수행은 레플리카 지연을 발생시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; : DDL이 수행이 마스터에서 실행을 완료 하고, 이어 슬레이브에서 DDL 연산이 완료된후에야 슬레이브에서 DML 수행이 될 수 있기 때문이다. (문제는 이게 거의 몇시간 가량까지 넘어가는 경우들이 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;2. innodb_online_alter_log_max_size의 값을 넘어가게 되면 오류가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;3. 많은 데이터를 가진 table을 변경할 시, 높은 I/O 사용량을 유발할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Instant&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MySQL 8.0.12 버전에 추가된 알고리즘으로 메타 정보만 수정하여 변경 사항을 반영한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;Data dictionary의 metadata만 변경하기 때문에 Table에 metadata lock을 걸지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Table data에도 영향을 받지 않기 때문에 바로 반영한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 제한 사항이 많다라는 단점이 있다. (컬럼 추가시 위치 지정 불가, &lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;FULLTEXT 인덱스를 포함하는 테이블에는 사용 불가 등&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[ MySQL에서 지원하는 Online DDL의 성능 확인 ]&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDL 마다 수행시간이 모두 다르다. 때문에 큰 테이블에서 DDL을 실행하기전에 다음을 수행해야 한다. [2]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( 참고 :&lt;span&gt; &lt;/span&gt;&amp;nbsp;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 테이블 구조를 clone&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. clone한 테이블에 소량의 데이터를 넣는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. DDL을 수행해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. &amp;ldquo;rows affected&amp;rdquo;가 0인지 아닌지 체크한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; : &quot;만약&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;0이 아니라면, special planning이 필요하다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;예를 들어, 예정된 downtime시간에 수행하거나, replica server에 하나씩 수행한다.&quot; 라고 공식 문서에서 제안하고 있다. 바로 이런 경우에 대해서 별도의&amp;nbsp;&lt;span data-token-index=&quot;0&quot;&gt;Online Schema Change Tool이 필요한 것이며, 이에 대해서는 앞으로 알아보도록 하겠다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[ MySQL 에서 지원하는 Online DDL 제한사항 ]&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대형 테이블의 온라인 DDL 작업에 적용된다. [3]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Online DDL 작업을 일시 중지하거나 Online DDL 작업에 대한 I/O 또는 CPU 사용량을 조절하는 메커니즘이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; : 때문에 많은 데이터를 가진 table을 변경할 시, 해당 DDL이 매우 높은 I/O 사용량을 유발할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 온라인 DDL 작업의 롤백 비용이 많이 들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 장기 실행 온라인 DDL 작업으로 인해 복제 지연(replication lag)이 발생할 수 있다. &lt;br /&gt;&amp;nbsp; &lt;span&gt;&amp;nbsp; &amp;nbsp;: DDL이 수행이 마스터에서 실행을 완료 하고, 이어 슬레이브에서 DDL 연산이 완료된후에야 슬레이브에서 DML 수행이 될 수 있기 때문이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&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;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;[ &lt;span data-token-index=&quot;0&quot;&gt;Online Schema Change Tool 의 등장 &lt;/span&gt;]&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 위에서 알아본 것처럼, 데이터가 많은 테이블에 대해서 Online DDL을 수행할 시에 문제가 발생할 수 있다. 하나의 DDL 작업이 너무 오래걸리기 때문에 슬레이브가 밀리고, 높은 I/O와 CPU 사용량을 유발하게 된다. 이 문제를 해결하는 방법은 단순하다. DDL 작업을 분해하고, 대규모 작업을 여러개의 작은 작업으로 분할하여 단일 작업 시간을 줄이는 것이다. MySQL online DDL 도구( Pt OSC, Facebook OSC, oak online alter table, GH OST 등 )는 일반적으로 DDL 작업을 분해하는 데 사용된다. 이러한 도구들의 큰 아이디어는 사실 비슷하다. 원본 테이블의 미러 테이블을 만들고 먼저 테이블 구조 변경을 수행한 다음, 원본 테이블의 전체 데이터와 변경된 데이터를 동기화한다. 동기화가 완료되면 이 두 테이블을 swap 한다. 따라서 단일 DDL 작업으로 인한 레플리카 지연을 피할 수 있다. 주요 차이점이자 핵심 과제는 &lt;b&gt;&quot;이 과정 동안 발생하는 DML(update, delete, insert 요청)들을 어떻게 처리할 것이냐?&quot;&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;스크린샷 2023-01-11 오후 9.57.41.png&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I2KuW/btrVYjKQFzO/ymD0bchOXqxuQi2MFXCmmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I2KuW/btrVYjKQFzO/ymD0bchOXqxuQi2MFXCmmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I2KuW/btrVYjKQFzO/ymD0bchOXqxuQi2MFXCmmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI2KuW%2FbtrVYjKQFzO%2FymD0bchOXqxuQi2MFXCmmK%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;453&quot; height=&quot;234&quot; data-filename=&quot;스크린샷 2023-01-11 오후 9.57.41.png&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;720&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;Ref.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[1] &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[2] &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[3] &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-limitations.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-limitations.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[4] Online DDL 진행 상태와 Row log buffer 사용량 확인 :&amp;nbsp;&lt;a href=&quot;https://small-dbtalk.blogspot.com/search?q=online+ddl&quot;&gt;https://small-dbtalk.blogspot.com/search?q=online+ddl&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>mangdo</author>
      <guid isPermaLink="true">https://doing7.tistory.com/166</guid>
      <comments>https://doing7.tistory.com/166#entry166comment</comments>
      <pubDate>Tue, 15 Nov 2022 14:24:38 +0900</pubDate>
    </item>
    <item>
      <title>잘 쓰고있던 MySQL 대신 MongoDB를 도입한 썰.ssul</title>
      <link>https://doing7.tistory.com/163</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 사내에서는 메인 데이터베이스는 MySQL, 사용목적에 맞게 Redis, Elastic Search, Bigquery 다양한 데이터베이스를 쓰고 있다. 하지만, 새로운 요구사항의 데이터의 적재가 필요한 상황을 맞이하게 되었다. 일단 운영하고 있는 데이터베이스에 때려 넣을까 솔직히 고민했지만, 생각하면 생각할수록 요구사항에 맞는 데이터베이스가 없었고, 새로운 데이터베이스인 MongoDB를 도입하게 되었다. 오늘은 그 썰을 풀어볼까 한다.&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;스크린샷 2022-11-05 오후 7.59.11.png&quot; data-origin-width=&quot;1374&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDWgKt/btrQr3eDt9r/VwPhhr8UZ0rb2XjgpX6YM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDWgKt/btrQr3eDt9r/VwPhhr8UZ0rb2XjgpX6YM1/img.png&quot; data-alt=&quot;두둥두둥&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDWgKt/btrQr3eDt9r/VwPhhr8UZ0rb2XjgpX6YM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDWgKt%2FbtrQr3eDt9r%2FVwPhhr8UZ0rb2XjgpX6YM1%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;490&quot; height=&quot;275&quot; data-filename=&quot;스크린샷 2022-11-05 오후 7.59.11.png&quot; data-origin-width=&quot;1374&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;두둥두둥&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 요구사항은 다음과 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;bull; 방대한 양의 비정형 데이터를 적재해야하는 상황 (feat.&lt;a href=&quot;https://doing7.tistory.com/160&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;팔로잉 기반&lt;span&gt;&amp;nbsp;&lt;/span&gt;피드 아키텍처 설계하기&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;bull; 정합성은 조금 떨어지더라도, 고가용성을 지원해야한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;데이터 포맷이 향후 달라질 가능성이 있어서 유연한 스키마 구조를 가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;bull; 방대한 양을 감당할 수 있도록 높은 확장성을 가지고 있어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;bull;&amp;nbsp;엄청난 빈도의 쓰기 연산을 감당할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;bull; 조인은 필요없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;MongoDB 장점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;bull; 뛰어난 Read/Write 성능&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;높은 확장성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;bull;&amp;nbsp;데이터와 트래픽 증가에 따라 수평확장(scale-out) 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;bull;&amp;nbsp;MongoDB는 데이터 샤딩을 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;bull;&amp;nbsp;이러한 샤딩과정은 서비스 중단없이 온라인으로 진행된다. 만약 특정 샤드에 데이터가 몰리면 다른 샤드로 데이터를 이동 시켜 전반적으로 모든 샤드가 균등하게 데이터를 저장할 수 있도록 해주며, 이러한 동작을 밸런싱이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;스키마 프리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;bull;&amp;nbsp;사용할 컬럼을 미리 정의하지 않고 언제든지 필요한 시점에 데이터 저장가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;bull;&amp;nbsp;하지만 MongoDB는 모든 부분에 있어서 스키마 프리라고 보기는 어렵다, MongoDB는 보조 인덱스를 생성할 수 있는데, 보조인덱스는 스키마 프리가 아니라 항상 인덱스르 구성하는 필드를 정의해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;MongoDB 단점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;복잡한 Join 사용시 성능 제약&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;bull;&amp;nbsp;RDBMS와 같지는 않지만 Aggregation 기능을 이용하면 RDBMS와 비슷한 형태의 조인 처리를 수행할 수 있다. 하지만 샤딩 환경에서는 성능이 느려지기 때문에 권장하지 않는다고 한다.&lt;/p&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&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;트랜잭션 지원&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;bull;&amp;nbsp;MongoDB 4.0이 릴리즈 되면서 Replica Set에서 작동하는 다중 도큐먼트 트랜잭션에 대한 지원을 추가되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;bull;&amp;nbsp;또, MongoDB 4.2의 릴리스와 함께 다중 도큐먼트 트랜잭션에 대한 지원이 Sharded Cluster로 확장되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;bull;&amp;nbsp;(짝수 버전이 안정화 버전, 홀수는 개발중인 버전)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;bull;&amp;nbsp;하지만 트랜잭션 지원이 RDBMS 보다는 미약하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;MongoDB 특징&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;BSON 형태로 저장&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;bull;&amp;nbsp;JSON 데이터를 그대로 저장하는 것이아닌, BSON(Binary Json)으로 변환하여 저장하므로 공백이나 마크업 문자로 인해 부가적으로 저장공간이 변하진 않는다.&lt;/p&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&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;커서 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;bull;&amp;nbsp;MongoDB는 쿼리 결과로 커서를 사용, 이 커서를 통해서 반복적으로 실제 도큐먼트 (레코드)를 가지고 올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;bull;&amp;nbsp;MongoDB에서 쿼리 결과로 커서를 반환하는 이유는 쿼리결과를 클리아이언트 서버 메모리에 모두 담아두지 않아도 처리할 수 있게하기 위해서다. 물론 커서를 읽을때마다 그때마다 도큐먼트를 가져오는 것은 아니고 필요할때마다 지정된 페이지 사이즈 단위로 서버로부터 전송받아 MongoDB 클라이언트 서버에 캐싱한 후에 유저에게 서비스하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;레플리카셋 구성 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X6dMW/btrQuIt5oay/Kztdsr8RGZt22bSPU9Rbhk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X6dMW/btrQuIt5oay/Kztdsr8RGZt22bSPU9Rbhk/img.jpg&quot; data-alt=&quot;https://www.mongodb.com/docs/manual/replication/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X6dMW/btrQuIt5oay/Kztdsr8RGZt22bSPU9Rbhk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX6dMW%2FbtrQuIt5oay%2FKztdsr8RGZt22bSPU9Rbhk%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;484&quot; height=&quot;183&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.mongodb.com/docs/manual/replication/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;bull; 프라이머리, 센컨드리. 아비터 조합!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;bull; 아비터란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;: 아비터 모드로 시작된 몽고디비는 레플리카 셋의 노드들과 히트비트만 주고 받으며, 데이터를 주고 받지 않는다. 프라이머리가 불능일때 프라이머리 노드의 선출에만 참여한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;도입시 주의점 1) 슬로우쿼리 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;[ MongoDB 로그파일 ]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 100밀리초가 넘게 걸린 쿼리는 MongoDB 서버의 로그파일에 모두 로깅된다. 100밀리초는 기본값이며, MongoDB 서버의 설정파일에서 slowMs 옵션을 조정하면된다. MongoDB 서버의 로그레벨을 디폴트 값 보다 더 자세히 또는 더 단순하게 변경하고자 한다면, 다음과 같이 db.setLogLevel()명령을 통해 서브 모듈별로 로그레벨을 조정할 수 있다. MongoDB 로그 사용시 주의점은 로그의 사이즈가 무제한으로 늘어난다는 것이다.이때문에 용량 문제가 생길 수 있으니, logrotate 설정이 필요하다.[1] &lt;span style=&quot;background-color: #ffffff; color: #555555;&quot;&gt;이번 포스팅이 너무 길어져서 다른 포스팅에서 더 구체적으로 설명해두었다. &lt;a href=&quot;https://doing7.tistory.com/161&quot;&gt;MongoDB에 Logrotate 설정하기&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;script src=&quot;https://gist.github.com/mangdo/d61acbf395537c1171d96dadd981a687.js&quot;&gt;&lt;/script&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;size18&quot;&gt;&lt;b&gt;[ MongoDB 쿼리 프로파일링 ]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; MongoDB 서버의 로그 파일에는 매우 많은 정보가 기록되어 슬로우 쿼리 수집이 어려울 수 있다. 이런 경우에는 데이터베이스별로 저장되는 쿼리 프로파일링을 이용하는 것이 좋다. MongoDB는 100ms 이상 걸린 슬로우 쿼리에 대해서 해당 컬렉션이 존재하는 DB의 system.profile 컬렌션에 기록한다.&lt;/p&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&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;프로파일링 레벨 조정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;주의 ) MongoDB의 로그파일에는 적용되지 않고, system.profile 컬렌션에 기록되는 슬로우 쿼리 로깅에만 적용된다.&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/mangdo/fd0125684ea89fb78169c61be59d9fb9.js&quot;&gt;&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;제한 용량&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; system.profile은 1MB 정도의 슬로우 쿼리만 저장할 수 있다. 1MB 이상되면, 오래된 로그부터 삭제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;시간순 정렬하여 확인 쿼리&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;슬로우 쿼리 로그를 확인할 때는 다음과 같이 역순으로 정렬해야 시간순으로 편히 볼 수 있다.&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/mangdo/dfdd6256204a9be692182cb221e80b7a.js&quot;&gt;&lt;/script&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;[ 슬로우 쿼리 분석 ]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;planSummary&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;: 쿼리의 실행 계획&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;keysExamined&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;: 쿼리를 처리하기 위해서 인덱스에서 읽은 인덱스 키의 개수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&amp;bull;&amp;nbsp;&lt;/b&gt;&lt;/b&gt;docsExamined&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;: 쿼리를 처리하기 위해서 컬렉션의 데이터 파일에서 읽은 도큐먼트의 개수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&amp;bull;&amp;nbsp;&lt;/b&gt;&lt;/b&gt;numYields&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;Yield : 장시간 쿼리가 실행되면 다른 커넥션들이 쿼리를 실행할 수 있도록 잠금을 해제했다가 다시 잠금을 획득하는 과정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;쿼리를 실행하는 도중에잠금을 얼마나 획득했는가를 의미&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;도입시 주의점 2) 쿼리 실행계획 분석 + 인덱스 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 처음 사용해보는 Database다보니, 쿼리 실행계획을 예측하기 어려웠다. 때문에 인덱스 설정에도 고민이 되었었다. 사용하는 모든 쿼리를 하나씩 돌려가면서 실행계획을 분석하고 인덱스를 만들어줬었다. 이번 포스팅이 너무 길어져서 다른 포스팅에서 더 구체적으로 설명해두었다. &lt;a href=&quot;https://doing7.tistory.com/162&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MongoDB에서 실행 계획 확인하는 방법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-05 오후 8.39.56.png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;858&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CQMwD/btrQrslIGTJ/8UWfGw9drkUantY2HHBX81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CQMwD/btrQrslIGTJ/8UWfGw9drkUantY2HHBX81/img.png&quot; data-alt=&quot;대충 이런식으로 쿼리 실행계획 보면서 인덱스 설정을 해주었다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CQMwD/btrQrslIGTJ/8UWfGw9drkUantY2HHBX81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCQMwD%2FbtrQrslIGTJ%2F8UWfGw9drkUantY2HHBX81%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;366&quot; height=&quot;296&quot; data-filename=&quot;스크린샷 2022-11-05 오후 8.39.56.png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;858&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;대충 이런식으로 쿼리 실행계획 보면서 인덱스 설정을 해주었다.&lt;/figcaption&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;b&gt;도입시 주의점 3) &lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;op: 'getmore' + &lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;batchSize&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1e1e1e;&quot;&gt;&amp;nbsp; MongoDB 클라이언트는 기본 배치 크기(default batch size)가 설정되어있고, 필요에 따라 이 값을 변경해 성능을 향상시킬 수 있다. 쿼리로그를 하나씩 보다보니 op:getmore 이 비정상적으로 많이 발생하는 것을 발견했고, 이를 수정했었습니다.&lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;원래 MongoDB에서는 batchSize나 limit을 정해주지 않으면, 기본적으로 batchSize를 101로 지정한다. (batch 한번당 데이터가 1MB가 넘어가지 않는 선에서) 하지만 &lt;span style=&quot;color: #1e1e1e;&quot;&gt;당시 사용하던 &lt;span style=&quot;color: #1e1e1e;&quot;&gt;MongoDB 클라이언트에서&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;batchSize의 default가 2였기 때문에 getMore를 계속 실행한다는 문제점이 있었다. 실제로 LINE 기술 블로그에서는 이때문에 성능 문제를 겪었다고 한다. [3]&lt;/span&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;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;script src=&quot;https://gist.github.com/mangdo/743cfa6ad7687cb76e9bed2b4ef6cdff.js&quot;&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;MongoDB 적용은 사실 상당히 가성비있게 적용했었다. 주어진 시간이 그리 많았던 것은 아니기때문에, 우선 Real MongoDB 책을 빠르게 훑었다.[4] 책 중에서는 빠른 도입 + 성능을 위해서 레플리카 셋, 인덱스, 데이터 모델링, 쿼리 최적화 부분만 힘줘서 읽고 나머지는 넘기거나 빠르게 훑었다. 이후에는 &amp;ldquo;don't use mongodb&amp;rdquo; &amp;ldquo;mongoDB 장애&amp;rdquo; 키워드로 검색하면서 최대한 장애를 미리 예방하려고 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 도입한지 6개월정도가 되었는데 아직까진 크게 문제없이 사용하고 있다. MySQL을 완벽하게 대체할 수 있다! 라는 의견에는 아직까진 모르겠지만, MongoDB만의 장점이있고, 만약 정합성이 중요하지 않은 기능이라면 적극적으로 사용해도 좋을 DB라고 생각한다. 돌이켜보면 이때 처음으로 RDBMS이 아닌, NoSQL인 MongoDB를 공부하면서 설레고 재밌어했었던것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[1] &lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://www.mongodb.com/docs/manual/reference/method/db.setLogLevel&quot;&gt;https://www.mongodb.com/docs/manual/reference/method/db.setLogLevel&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[3] &lt;a href=&quot;https://engineering.linecorp.com/ko/blog/LINE-integrated-notification-center-from-redis-to-mongodb&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://engineering.linecorp.com/ko/blog/LINE-integrated-notification-center-from-redis-to-mongodb&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[4] 대용량 데이터 처리를 위한 Real MongoDB : 이성욱, 2018, 위키북스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>디버깅중  </category>
      <author>mangdo</author>
      <guid isPermaLink="true">https://doing7.tistory.com/163</guid>
      <comments>https://doing7.tistory.com/163#entry163comment</comments>
      <pubDate>Sat, 5 Nov 2022 20:54:15 +0900</pubDate>
    </item>
    <item>
      <title>MongoDB에서 실행 계획 확인하는 방법</title>
      <link>https://doing7.tistory.com/162</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;M&lt;/b&gt;&lt;b&gt;ongoDB에서 실행계획을 선택하는 방법&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;주요 로직&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;bull; RDBMS 와 비교했을 때 MongoDB는 각 컬렉션에 대한 통계 정보가 거의 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;MongoDB는 규칙 기반의 옵티마이저 (rule-based optimizer)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;규칙기반 옵티마이저는 RDBMS에서 허점이 많아서 거의 사용하지 않는 최적화 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;bull;&amp;nbsp;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;MongoDB는 실행 계획 후보를 모두 동시에 실행(쿼리의 일부분만)해보는 형태로 단점을 보완&lt;/span&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;&amp;nbsp; &amp;nbsp; &amp;nbsp;1) 한 쿼리에 대해 실행계획 후보가 3개&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;2) &lt;/span&gt;3개를 동시에 쿼리 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;3)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;3개 중에서 어느 하나라도 먼저 실행이 완료되거나 101건의 도큐먼트를 먼저 검색하는 경우 멈추게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;101건이 많다고 생각되면 기본값을 변경할 수 있다. 하지만&amp;nbsp;바꾸려면 collFration과 works 옵션도 같이 조정하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull;&amp;nbsp;이때 선택된 플랜을 winning plan이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/mangdo/7c09c3acef01add9a2c50d49c6601114.js&quot;&gt;&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;플랜 캐시&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;플랜 캐시란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;최적의 쿼리 실행계획이 수립되기 위해서 실행 계획을 직접 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;이런 이유로 실행 계획을 수립하는 과정 자체도 많은 시간이 걸릴 수 있어서 캐시를 이용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;캐시 사이즈&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull;&amp;nbsp;최대 5000개(MongoDB 3.6 기준)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull;&amp;nbsp;5000개가 넘으면 사용되지 않는 실행계획이 캐시에서 삭제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull;&amp;nbsp;해당 컬렌션의 인덱스가 추가 또는 삭제되는 경우에도 캐시 삭제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull;&amp;nbsp;5000이라는 숫자는 db.runCommand( {setParameter:1, internalQueryCacheSize: 6000 }) 변경 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;같은 패턴의 쿼리인지 확인하는 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull; 같은 패턴의 쿼리라면 플랜캐시 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Query Shape&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;쿼리 조건(query predicate)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;정렬조건(sort)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;조회필드(projection)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;플랜캐시의 성능이 아직도 유효한지 체크하는 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;플랜캐시에서 꺼내기 &amp;rarr; &lt;b&gt;성능 재평가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;지정된만큼의 도큐먼트만 읽어서 internalQueryPlanEvalueionMaxResults 에 정의한 수만큼 도큐먼트를 가져올 수 있는지 판단하는 형태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;최초 실행계획을 수립할 때 work() 함수의 호출 횟수 x internalQueryCaheEvictionRatio&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;쿼리 재평가 과정도 결국 쿼리의 일부를 실행해보는 과정이다. 만약 실행 계획 수립과정이 커리 처리 성능에 미치는 영향이 큰 서비스라면 이 옵션을 좀 더 낮은 수치로 조정해서 성능 테스트를 해봐도 좋을 듯하다.. 라고 권유하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실행 계획 확인&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;QueryPlanner&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 디폴트이며, 가장 단순한 실행계획 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 어떤 인덱스를 사용했는가 정도를 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&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;script src=&quot;https://gist.github.com/mangdo/ed408f7b0e59cf43d50c5245b25ef6da.js&quot;&gt;&lt;/script&gt;
&lt;/ul&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;script src=&quot;https://gist.github.com/mangdo/64f4fb3e9cb1bd28553755720e129fd5.js&quot;&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;ExecutionStats&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 선택한 실행계획의 상세한 내용까지 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실행 쿼리&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/mangdo/bef434fc51260bcf397e81ff67a07ea5.js&quot;&gt;&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;주요 확인 요소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;bull; &lt;/span&gt;&lt;/b&gt;&lt;/b&gt;nReturned&lt;/b&gt; : 실제 클라이언트로 반환된 도큐먼트의 건수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;executionTimeMillis&lt;/b&gt; : 각 스테이지의 실행 시간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;totalKeyExamined&lt;/b&gt; : 쿼리를 처리하기 위해서 인덱스에서 읽은 인덱스 키의 개수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;totalDocsExamined : 쿼리를 처리하기 위해서 컬렉션의 데이터 파일에서 읽은 도큐먼트의 개수&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;script src=&quot;https://gist.github.com/mangdo/64f4fb3e9cb1bd28553755720e129fd5.js&quot;&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;/div&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;b&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;+) works?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;bull; &lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;각 스테이지들이 자식 스테이지의 work()를 호출한 횟수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;work() 호출은 도큐먼트 단위로 호출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;work()함수 호출은 대표적으로 다음 3종류의 리턴 값을 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;ADVANCE&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;: 스테이지의 처리 결과 한건의 도큐먼트 또는 ID(프라이머리 키) 값을 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;NEED_TIME&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;: 스테이지 처리는 완료되었지만, 결과 도큐먼트나 ID 값은 반환되지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;: 주로, 블로킹 스테이지일때 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;: 대표적인 블로킹 스테이지는 인덱스를 사용하지 못하는 정렬이나 그룹핑 스테이지, 인덱스를 사용하지 못하는 필터링 스테이지 &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; ex) 인덱스를 이용해서 도큐먼트 한건을 읽었는데, 이 도큐먼트가 다른 조건(인덱스를 사용하지 못하는 경우)에 의해서 버려지는 경우다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; : 얼마나 도큐먼트를 읽고 버렸는지, 얼마나 정렬 수행을 했는지를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;IS_EOF&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; : 스테이지 처리를 완료했으며, 더 이상 읽을 도큐먼트나 ID가 없다.&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;b&gt;AllPlansExecution&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 모든 실행 계획 후보들까지 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실행 쿼리&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/mangdo/40e8e6bdf2770385638e2e358c1129c7.js&quot;&gt;&lt;/script&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;Explain Results&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;루트 스테이지에서 하위 스테이지를 콜한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;실제로 수행되는 것은 루트 스테이지에서 부터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;script src=&quot;https://gist.github.com/mangdo/d0f16cd53fe128a34fa9cac77c701a19.js&quot;&gt;&lt;/script&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;b&gt;stage 예시&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;COLLSCAN : 컬렉션 풀 스캔&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;IXSCAN : 인덱스 레인지 스캔&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;FETCH : 인덱스 레인지 스캔으로 읽은 인덱스키와 recordId를 이용해서 컬렉션의 도큐먼트를 읽기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;SORT&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;COUNT&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;LIMIT&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;GROUP&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;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;MongoDB Compass에서 실행시&lt;/span&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;스크린샷 2022-11-05 오후 7.12.13.png&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7fwwL/btrQr2mw3sU/SKSViDPwYR0KqU4xoDGEH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7fwwL/btrQr2mw3sU/SKSViDPwYR0KqU4xoDGEH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7fwwL/btrQr2mw3sU/SKSViDPwYR0KqU4xoDGEH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7fwwL%2FbtrQr2mw3sU%2FSKSViDPwYR0KqU4xoDGEH1%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;325&quot; height=&quot;567&quot; data-filename=&quot;스크린샷 2022-11-05 오후 7.12.13.png&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1190&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;Ref.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대용량 데이터 처리를 위한 Real MongoDB : 이성욱, 2018, 위키북스&lt;/p&gt;</description>
      <category>Database</category>
      <author>mangdo</author>
      <guid isPermaLink="true">https://doing7.tistory.com/162</guid>
      <comments>https://doing7.tistory.com/162#entry162comment</comments>
      <pubDate>Sat, 5 Nov 2022 19:18:02 +0900</pubDate>
    </item>
    <item>
      <title>MongoDB에 Logrotate 설정하기</title>
      <link>https://doing7.tistory.com/161</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Logroate&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; Logrotate&lt;/b&gt;는&amp;nbsp;특정 log 파일이 한 파일로 계속해서 크기가 커지는 문제를 해결하기 위해서, 분산 저장시에 사용한다. 하나의 log 파일에 log가 지속적으로 쌓이게&amp;nbsp;되면,log 확인이 필요한 경우 하나의 파일이 너무 방대해 확인이 어려워&amp;nbsp;지고, 디스크 용량 또한 낭비된다. Logrotate&lt;b&gt;는&lt;/b&gt;&amp;nbsp;Linux에서 log 관리를 위해 사용하는데, OS 설치시 기본적으로 설치되어 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Option&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;rotate [숫자] : log파일이 5개 이상 되면 삭제ex) rotate 5&lt;/li&gt;
&lt;li&gt;maxage [숫자] : log파일이 30일 이상 되면 삭제ex) maxage 30&lt;/li&gt;
&lt;li&gt;size : 지정된 용량보다 클 경우 rotate 실행ex) size +100k&lt;/li&gt;
&lt;li&gt;create [권한][유저] [그룹] : rotate 되는 로그파일 권한 지정ex) create 644 root root&lt;/li&gt;
&lt;li&gt;notifempty : 로그 내용이 없으면 rotate 하지 않음&lt;/li&gt;
&lt;li&gt;ifempty : 로그 내용이 없어도 rotate 진행&lt;/li&gt;
&lt;li&gt;monthly(월) , weekly(주) , daily(일) rotate 진행&lt;/li&gt;
&lt;li&gt;compress : 로테이트 되는 로그파일 gzip 압축&lt;/li&gt;
&lt;li&gt;nocompress : 로테이트 되는 로그파일 gzip 압축 X&lt;/li&gt;
&lt;li&gt;missingok : 로그 파일이 발견되지 않은 경우 에러처리 하지 않음&lt;/li&gt;
&lt;li&gt;dateext : 백업 파일의 이름에 날짜가 들어가도록 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;logrotate.conf는&amp;nbsp;Logrotate&amp;nbsp;실행의 모든 설정을 담당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;/etc/mongod.conf&lt;/b&gt;&lt;/h3&gt;
&lt;script src=&quot;https://gist.github.com/mangdo/bc5d1de555c41023fcab8a169298b610.js&quot;&gt;&lt;/script&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;logrotate.conf&lt;/b&gt;&lt;/h3&gt;
&lt;script src=&quot;https://gist.github.com/mangdo/cc43425b3cac81770f8a930212acf0df.js&quot;&gt;&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;USR1 시그널은 사용자 정의 시그널로 Log 파일을 re-open한다. logrotate로 Log Rotation하더라도 Log 파일을 re-open하지 않으면 rotate된 파일에 계속 쓰기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ref&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://m.blog.naver.com/PostView.naver&quot;&gt;https://m.blog.naver.com/PostView.naver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;amp;blogId=whiteyoung00&amp;amp;logNo=70026835092&quot;&gt;isHttpsRedirect=true&amp;amp;blogId=whiteyoung00&amp;amp;logNo=70026835092&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;mongod 서비스 재시작&lt;/b&gt;&lt;/h3&gt;
&lt;script src=&quot;https://gist.github.com/mangdo/2d31002af194328a063998620986907d.js&quot;&gt;&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;logrotate 확인&lt;/b&gt;&lt;/h3&gt;
&lt;script src=&quot;https://gist.github.com/mangdo/41791ee80e39a5e9125e86f938754914.js&quot;&gt;&lt;/script&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;스크린샷 2022-11-05 오후 6.38.58.png&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wxNTN/btrQtUOY6RN/coKpEY8vKLqrkr0iEFHx90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wxNTN/btrQtUOY6RN/coKpEY8vKLqrkr0iEFHx90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wxNTN/btrQtUOY6RN/coKpEY8vKLqrkr0iEFHx90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwxNTN%2FbtrQtUOY6RN%2FcoKpEY8vKLqrkr0iEFHx90%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;1424&quot; height=&quot;178&quot; data-filename=&quot;스크린샷 2022-11-05 오후 6.38.58.png&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Database</category>
      <author>mangdo</author>
      <guid isPermaLink="true">https://doing7.tistory.com/161</guid>
      <comments>https://doing7.tistory.com/161#entry161comment</comments>
      <pubDate>Sat, 5 Nov 2022 18:34:21 +0900</pubDate>
    </item>
    <item>
      <title>팔로잉 기반 피드 아키텍처 설계하기</title>
      <link>https://doing7.tistory.com/160</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;100만 구독자, 100k 팔로워를 가지고 있는 인플루언서, 연예인도 아니지만 수많은 구독자를 가지고있는 일반인들을 요즘에는 흔하게 찾아볼 수 있다. 그리고 이런 계정을 팔로워하면, 당연히 내 피드에 떠야한다고 생각했다. 당연하다. 하지만 내가 직접 구현하려고 하니 뭐 하나 당연한게 없었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 사내에서 &quot;팔로잉 - 팔로잉 기반 피드&quot;프로젝트에 백엔드 엔지니어로써 참여했었다. DB 설계, API 설계, 기능 개발, 배포이후 모니터링 전반적인 작업들을 담당했었다. 이번 포스팅에서는 팔로잉의 아키텍처 설계단계에서 고민했었던 지점들에 대해서 이야기해보려고한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-03 오후 8.45.35.png&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;1404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqBX8U/btrQj7a7Oqa/BhjPvcKekZDvZ8PFTEBrQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqBX8U/btrQj7a7Oqa/BhjPvcKekZDvZ8PFTEBrQ0/img.png&quot; data-alt=&quot;실제 LINER 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqBX8U/btrQj7a7Oqa/BhjPvcKekZDvZ8PFTEBrQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqBX8U%2FbtrQj7a7Oqa%2FBhjPvcKekZDvZ8PFTEBrQ0%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;505&quot; height=&quot;532&quot; data-filename=&quot;스크린샷 2022-11-03 오후 8.45.35.png&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;1404&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실제 LINER 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[ IN 연산으로 처리하기 ]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;팔로잉 기반 아키텍처 구조를 생각할때 제일 간단하게 생각해낼 수 있는 방법일 것이다. 내가 따르고 있는 친구들의 게시글을 조회하기 위해서, 내가 따르는 사람들을 모두 IN에 넣어 이들의 게시글을 조회하는 방식이다. 하지만 문제점이 있다. 팔로잉의 숫자가 늘어나게 되면 인덱스를 제대로 타지 못한다는 문제다.&amp;nbsp;&lt;br /&gt;&amp;nbsp; 인스타그램에서는 &lt;span style=&quot;background-color: #ffffff; color: #212529;&quot;&gt;7,500개의 팔로우 제한이 있으며(자신이 팔로우 하는것에 대하여만, 남이 나를 팔로잉하는 것에는 수 제한이 없음), 트위터와 페이스북에서는 5,000개의 팔로우 제한이 있다.[1][2][3] 이에 기반하여 서비스에 1000명정도의 팔로우 제한을 걸어둔다고 생각을 했고, &lt;/span&gt;&amp;nbsp;1000명의 팔로우를 한 유저가 있을때 다음과 같은 쿼리가 나갈 수 있다. 'select * from (게시글 테이블명) where user_id in (1,2,3,4,5...1000)' 하지만 이런식으로 in절에 값이 너무 많이 들어가게되면 인덱스를 제대로 타지 못하게되면서 성능 문제가 생길 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;* eq_range_index_dive_limit&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;: IN 절내에 값의 크기가 eq_range_index_dive_limit값(default 200)을 초과할 경우 실행계획을 세울 때, &lt;span style=&quot;background-color: #ffffff;&quot;&gt;index dive 방식이 아닌 &lt;span style=&quot;background-color: #ffffff;&quot;&gt;index statistics 방식을 사용하도록 한다. index dive란, &lt;span style=&quot;background-color: #ffffff;&quot;&gt;실행계획을 세울때 index range scan을 위한 최적화시 row를 예측하기위해 직접 index를 이용해서 row수를 예측하도록 하는 방식&lt;/span&gt;이며&lt;span style=&quot;background-color: #ffffff;&quot;&gt;[4]&lt;/span&gt;, &lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;index statistics방식은 MySQL 5.6부터 적용된 방식으로, index dive가 아닌 인덱스 통계정보를 바탕으로 실행계획을 세우도록 하는 방식이다. 통계정보가 정확할때도 있지만, 100% 신뢰할 수는없기 때문에 생각지 못한 성능저하를 발생시킬 수 있다.[5] [6]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;* &amp;nbsp;range_optimizer_max_mem_size&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;: 수행하는 쿼리의 용량이 range_optimizer_max_mem_size 값을 초과하게되면 옵티마이져가 풀스캔 혹은 이외의 인덱스를 태우도록 한다. [7]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;* &lt;/b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;b&gt;MATERIALIZED select type&lt;/b&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp;: 실제로 예상쿼리인 select * from (게시글 테이블명) where user_id in (1,2,3,4,5...1000) order by id desc limit 20 의 실행계획을 돌려보았을때 &lt;span style=&quot;background-color: #ffffff;&quot;&gt;MATERIALIZED 라는 select type이 발생했었다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&amp;nbsp; : &lt;span style=&quot;background-color: #ffffff;&quot;&gt;MATERIALIZED은 MySQL &lt;span style=&quot;background-color: #ffffff;&quot;&gt;5.6 버전에 추가된 셀렉트 타입으로, IN 절 내의 서브쿼리를 &lt;u&gt;임시테이블로 만들어&lt;/u&gt; 조인을 하는 형태로 최적화를 해준다. 참고로 5.6 버전에서는 IN절내에 서브쿼리가 존재하는 경우 매 레코드 마다 서브쿼리를 실행시키는 형태로 수행되었다고 한다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[ 다른 소셜 네트워크 서비스 아키텍처 ]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;이정도로 생각하고나니, MySQL의 쿼리 성능 개선작업만으로는 될 수 있는 문제가 아니라고 생각이 들었고, 다른 소셜형 네트워크 서비스들에서는 어떤 아키텍처를 사용하고 있는지 조사하기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 포스팅 전송, 즉 팬아웃(fanout)은 어떤 사용자의 새 포스팅을 그 사용자와 친구 관계에 있는 모든 사용자에게 전달하는 과정이다. 널리 알려진 소셜형 네트워크 서비스들의 팬아웃 기능에는 크게 두가지모델이 있다. 하나는 쓰기 시점에 팬아웃하는 모델(fanout-on-write, 또는 push 모델)이고, 다른 하나는 읽기 시점에 팬아웃 하는 모델(fanout-on-read, 또는 pull 모델)이다. fanout-on-write모델의 대표 서비스는 트위터가 있고, fanout-on-read 모델의 대표 서비스로는 페이스북이 있다. 각각의 모델들에 대해서 간략하게 살펴보고, 트위터와 페이스북이 왜 그 모델들을 선정하였으며, 선택한 모델들의 한계점을 넘기기위하여 어떤 노력들을 했는지 알아보겠다.&lt;/p&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&gt;&lt;span&gt; Push &lt;/span&gt;모델&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp;: 새로운 포스팅을 기록하는 시점에 뉴스피드를 갱신하게 된다. 다시 말해, 포스팅이 완료되면 바로 해당 사용자의 캐시에 해당 포스팅을 기록하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&amp;nbsp; : 새 포스팅이 기록되는 순간에 뉴스 피드가 갱신됨으로(on-demand가 아닌 pre-computed), 뉴스피드를 읽는데 드는 시간이 짧아진다는 장점이 있다. 하지만 서비스를 잘 사용하지 않는 비활성화 유저의 뉴스 피드도 갱신해야하므로 컴퓨팅 자원이 낭비될 수 있다는 문제점이 있다. 또한 팔로워가 많은 사용자의 경우, 친구 목록을 가져고오고 그 목록에 있는 사용자 모두의 뉴스피드를 개신하는 데 많은 시간이 소요될 수 있다. 흔히 hot-key, hot-spot 문제, 유명인 문제, 저스틴 비버 문제등등...이라고 부르는 문제이다.&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;b&gt;* Pull&lt;span&gt;&amp;nbsp;모델&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp;: 피드를 읽어야하는 시점에 뉴스 피드를 갱신하는 요청 기반(on-demand)모델이다. 사용자가 본인의 피드를 로딩하는 시점에 새로운 포스트를 가져오게 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&amp;nbsp; : 비활성화된 사용자에 대해 어떠한 컴퓨팅 자원도 소모하지 않아도 된다는 장점이 있다. 또한 데이터를 친구 가각에 푸시하는 작업이 필요 없으므로 핫키문제도 생기지 않는다. 하지만, 뉴스피드를 읽는데 많은 시간이 소요될 수 있다는 단점이 존재한다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[ 페이스북 : Pull&lt;span&gt;&amp;nbsp;&lt;/span&gt;모델 ]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 페이스북은 소셜 그래프를 push 보다는 최대한 pull 하는 방식을 선택해왔다. 이러한 구현 전략은 DB에 많은 읽기를 요구하게된다. 이때문에 페이스북은 주커버그가 초기 페이스북을 만들던 2005년 부터 memcache를 사용해왔다. 하지만, 페이스북 피드의 요구사항이 복잡해짐에 따라서 TAO(The Associations and Objects)를 도입하게 되었다. TAO는 그래프 자료구조의 write-through 분산캐시다. TAO의 키워드를 하나씩 이야기해보면서 좀 더 살펴보겠다.&lt;/p&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 data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;그래프 자료구조&lt;/span&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;스크린샷 2022-10-30 오후 10.14.09.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;952&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFGTeF/btrPU03IyeB/XJwV7FFb0q1zv2Uk6BOKPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFGTeF/btrPU03IyeB/XJwV7FFb0q1zv2Uk6BOKPK/img.png&quot; data-alt=&quot;https://www.usenix.org/system/files/conference/atc13/atc13-bronson.pdf&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFGTeF/btrPU03IyeB/XJwV7FFb0q1zv2Uk6BOKPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFGTeF%2FbtrPU03IyeB%2FXJwV7FFb0q1zv2Uk6BOKPK%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;390&quot; height=&quot;368&quot; data-filename=&quot;스크린샷 2022-10-30 오후 10.14.09.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;952&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.usenix.org/system/files/conference/atc13/atc13-bronson.pdf&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; &amp;ldquo;앨리스가 Golden Gate Bridge에 가서 친구인 bob을 태그했다, Golden Gate Bridge에 간 글을 bob의 친구인 Cathy가 댓글을 남겼고, 그 댓글에 Cathy의 친구인 David가 좋아요를 눌렀다.&amp;rdquo; 위 그림이 표현하는 문장이다. 이 문장을 만약에 내가 가장 처음에 말했었던 OR 을 사용했었다면, 피드를 구성하기 위해서 수많은 테이블과 캐시들을 헤집고 다녔어야할 것이다. &amp;ldquo;어떻게 하면 최소한의 구조로 최대한의 표현력을 가지며 피드 정보를 빠르게 읽을 수 있을까?&amp;rdquo;에 대한 고민의 답으로 페이스북은 그래프 자료 구조를 사용하였다.&lt;/p&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 data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;성격이 다른 두개의 DB(Memcache, MySQL) 유지에 대한 어려움&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ywdgi/btrPQ40gXaa/PbaTNMkbVapVbwG8kvHw7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ywdgi/btrPQ40gXaa/PbaTNMkbVapVbwG8kvHw7k/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1712&quot; data-origin-height=&quot;826&quot; data-filename=&quot;스크린샷 2022-10-30 오후 10.23.34.png&quot; width=&quot;489&quot; height=&quot;236&quot; style=&quot;width: 50.3317%; margin-right: 10px;&quot; data-widthpercent=&quot;50.92&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ywdgi/btrPQ40gXaa/PbaTNMkbVapVbwG8kvHw7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fywdgi%2FbtrPQ40gXaa%2FPbaTNMkbVapVbwG8kvHw7k%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;1712&quot; height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O8Owl/btrPQxBXPNB/DbSzkfg2JQIJbE3efxBhS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O8Owl/btrPQxBXPNB/DbSzkfg2JQIJbE3efxBhS1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;780&quot; data-filename=&quot;스크린샷 2022-10-30 오후 10.23.49.png&quot; width=&quot;331&quot; height=&quot;166&quot; style=&quot;width: 48.5055%;&quot; data-widthpercent=&quot;49.08&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O8Owl/btrPQxBXPNB/DbSzkfg2JQIJbE3efxBhS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO8Owl%2FbtrPQxBXPNB%2FDbSzkfg2JQIJbE3efxBhS1%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;1558&quot; height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TAO 이전에는 페이스북의 웹서버가 MySQL에 직접 접근하여 소셜 그래프를 읽거나 작성했으며, memcahe를 lookaside 캐시로 사용했다. memcache는 키-값 데이터 모델이 있는 범용 네트워크 인메모리 데이터 저장소다. 캐시 누락시, 자동으로 채우거나 캐시 일관성을 유지해주지는 않았다. 때문에 엔지니어들은 MySQL과 memcache 라는 두개의 데이터 저장소에, 서로 다른 데이터 모델로 작업을 했어야했다. Memcahce-MySQL 조합을 효율적으로 사용하기 위해서는 시스템 내부에 대한 상당한 지식이 필요했고, 이로 인한 버그와 성능문제로 이어지는 실수가 있었다. 또한 제품의 요구사항이 변함에 따라 테이블 스키마를 변경하려면 각 데이터 베이스 운영자간의 조정이 필요했다. TAO는 이러한 작업들을 추상화하여 lookaside 캐시 아키텍처의 근본적인 단점을 피할 수 있었다. &lt;b&gt;TAO는 persistence DB로 MySQL을 계속 사용하지만, 데이터베이스에 대한 접근을 중재하고 자체 그래프 인식 캐시를 사용한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시의 최종 일관성 유지를 위한 leader-follower 캐시 아키텍처도 흥미롭게 읽었는데, 이는 Meta 기술블로그[8], 혹은 논문의 4 Architecture로 가면 더 자세하게 알 수 있다.[9]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[ 트위터 : Push&lt;span&gt;&amp;nbsp;&lt;/span&gt;모델 ]&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chIdeH/btrPQ40h0iP/SJV4t08HLPSbH2plONJoCk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chIdeH/btrPQ40h0iP/SJV4t08HLPSbH2plONJoCk/img.jpg&quot; data-alt=&quot;http://content.time.com/time/specials/packages/article/0,28804,1901188_1901207_1901196,00.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chIdeH/btrPQ40h0iP/SJV4t08HLPSbH2plONJoCk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchIdeH%2FbtrPQ40h0iP%2FSJV4t08HLPSbH2plONJoCk%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;360&quot; height=&quot;235&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;http://content.time.com/time/specials/packages/article/0,28804,1901188_1901207_1901196,00.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 페이스북은 게시판 구조에서부터 시작해왔었다. 게시판의 필터 검색이라는 기능처럼, 피드를 구성해갔다. Pull 모델을 사용하고, 더 빠르게 조회가 가능하도록 캐시를 적극적으로 사용했다. 위 그림에서의 트위터 소개를 보면, friend with communicate, exchange of quick frequent answer 이라는 문장을 볼 수 있다. 애초에&amp;nbsp;&lt;span&gt;트위터라는 서비스는 메시지 전달이라는 관점에서 시작된 서비스다. 트위터의 트레이드 마크로 알려진 140자 제한은 SMS 글자수로 부터 시작되었다. 이처럼 SMS, 이메일 서비스와 흡사하게 시작한 트위터는 아키텍처에도 이러한 특징들을 담고 있다. 나의 메시지를 수신하는 사람들은 나를 팔로우하는 사람들이며, 결국 내가 메시지을 올린다는 것은 나를 팔로우하는 사람들의 받는 메시지함으로 수신되어야 한다는 것이다. 이메일의 경우에는 전체 문자를 배달하지만, 트위터의 경우에는 원본메시지는 하나만 두고, 팔로워들의 타임라인에는 참조키만 저장시키는 방식을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; 쓰기 요청에 비해 읽기 요청이 많은 서비스라면, 차라리 읽기에 부담을 주는 페이스북의 Pull 모델보다는 트위터의 Push 모델이 좋은 선택일 수 있다. &lt;/span&gt;하지만 이 또한 꽤 높은 비용이 들 수 있다. 내가 팔로우하고 있는 누군가가 트윗을 쓸 때마다 내 타임라인에 트윗의 참조 키가 배달된다. 저스틴 비버를 예로 들어 생각해보면, 저스틴 비버는 현재 1.1억 팔로워를 가지고 있다. 1억명 중에는 사실 트위터를 방문한지 한달이 넘은 이탈유저들도 많이 포함되어있을 것이다. 하지만 이 모든 사람들에게 트윗의 참조키를 전송해야만 한다. 대략적으로 계산을 해보면 8바이트(참조 키 크기) x 1억 = 800MB나 된다. 이런 인플루언서들이 &lt;span&gt;&lt;/span&gt;트윗을 작성하는 순간에 엄청난 양의 데이터를 저장해야 하는데, DB로 이런 트래픽을 실시간으로 받아내는 것은 어렵기 때문에 사용자의 트윗 수신함, 즉 타임라인은 Redis 클러스터로 구성하며[10] 결국에는 MySQL로 저장하고, Gizzard(&lt;span style=&quot;background-color: #ffffff; color: #657786;&quot;&gt;&lt;span&gt; twitter &lt;/span&gt;sharding and replication framework on top of MySQL&lt;/span&gt;)를 사용하고 있다고 한다.[11]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Pull(fanout-on-read) 모델은&lt;span&gt; 사용자가 피드를 조회할때 큰 연산을 필요로 하고, Push(fanout-on-write) 모델은 사용자가 글을 게시할때 큰 연산을 필요로 한다. 때문에&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Pull 모델을 선택한다면 &quot;어떻게 하면 조회시간을 줄일 수 있느냐?&quot;가 관건이고, 페이스북은 이 문제를 해결하기위해서 조회를 더 효과적으로할 수있는 TAO를 만들었다. 이에 반해 &lt;/span&gt;&lt;span&gt;Push(fanout-on-write) 모델은 &quot;이 많은 데이터들을 어떻게 저장시킬 것인가, 어떻게하면 빨리 저장시킬것인가?&quot;가 관건이고, 트위터를 이를 해결하기 위해서 DB에 최종 일관성과 MySQL 샤딩을 위한 Gizzard를 도입했다. 하나를 반드시 선택하기 보다는 pull과 push를 혼합으로 사용하는 방법도 있을 수 있다. 대부분의 사용자에 대해서는 Push 모델을 사용하고, 팔로워가 아주 많은 사용자의 경우에는 팔로어로 하여금 해당 사용자의 포스팅을 필요로 할때 가져가도록 하는 Pull 모델을 사용하는 방법이 될 수 있을 것이다. 이처럼 &lt;/span&gt;pull과 push의 혼합으로 해결할 수 있다는 논문이 Yahoo에서 나왔다.[12] 어떤 모델이 더 우수하기 보다는 서비스 특성과 현재 개발 상황에 맞게 선택하면 그것이 가장 최선의 방법이라고 생각한다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ref)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[1] &lt;a href=&quot;https://www.instafollowers.co/blog/limits-on-instagram&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.instafollowers.co/blog/limits-on-instagram&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[2] &lt;a href=&quot;https://mention.com/en/reports/instagram/followers/&quot;&gt;https://mention.com/en/reports/instagram/followers/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[3] &lt;a href=&quot;https://help.twitter.com/en/rules-and-policies/twitter-limits&quot;&gt;https://help.twitter.com/en/rules-and-policies/twitter-limits&lt;/a&gt;&lt;br /&gt;[4] &lt;a href=&quot;http://small-dbtalk.blogspot.com/2016/02/&quot;&gt;http://small-dbtalk.blogspot.com/2016/02/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;[5]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://jobc.tistory.com/216&quot;&gt;https://jobc.tistory.com/216&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[6] &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_eq_range_index_dive_limit&quot;&gt;https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_eq_range_index_dive_limit&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[7] &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html&quot;&gt;https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[8] &lt;a href=&quot;https://engineering.fb.com/2013/06/25/core-data/tao-the-power-of-the-graph/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://engineering.fb.com/2013/06/25/core-data/tao-the-power-of-the-graph/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[9] &lt;a href=&quot;https://www.usenix.org/system/files/conference/atc13/atc13-bronson.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.usenix.org/system/files/conference/atc13/atc13-bronson.pdf&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[10] &lt;a href=&quot;https://blog.twitter.com/engineering/en_us/a/2012/mysql-at-twitter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://blog.twitter.com/engineering/en_us/a/2012/mysql-at-twitter&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[11] &lt;a href=&quot;https://github.com/twitter-archive/gizzard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/twitter-archive/gizzard&lt;/a&gt;&lt;br /&gt;[12] &lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://hyse.org/pdf/distsyspapers/sigmod278-silberstein.pdf&quot;&gt;https://hyse.org/pdf/distsyspapers/sigmod278-silberstein.pdf&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/551588&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://d2.naver.com/helloworld/551588&lt;/a&gt;&lt;/p&gt;</description>
      <category>디버깅중  </category>
      <author>mangdo</author>
      <guid isPermaLink="true">https://doing7.tistory.com/160</guid>
      <comments>https://doing7.tistory.com/160#entry160comment</comments>
      <pubDate>Sun, 30 Oct 2022 18:16:45 +0900</pubDate>
    </item>
    <item>
      <title>함수형 프로그래밍은 그래서 왜 쓸까?</title>
      <link>https://doing7.tistory.com/159</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 현재 다니고 있는 회사에서는 함수형 프로그래밍을 코드 전반에서 적극적으로 사용하고 있다. 사내에서 사용하고 있는 언어가 Kotlin이다보니, Kotlin 을 더 잘 사용할 수 있는 방법에 대해 고민하다가 함수형 프로그래밍에 대해서 스터디를 진행했었다. 스터디를 진행하면서 적용해볼 수 있겠다 싶은 내용들은 실제로 프로덕션 코드에 녹이면서 나름대로 함수형 프로그래밍을 적극적으로 활용하고 있다라고 생각했다.&lt;br /&gt;&lt;br /&gt;&amp;nbsp; 어느날, 함수형 프로그래밍을 공부할까 고민하시던 같은 팀의 프론트 엔지니어 분이 &amp;ldquo;함수형 프로그래밍이 뭐에요? 도입하면 장점이 뭐에요?&amp;rdquo;라고 여쭤보셨다. 순간 말문이 막히는 것을 느껴졌고, 내가 함수형 프로그래밍을 사용하고 있다고 생각했지만, 그 장점이 무엇인지 대체 왜 써야하는건지에 대한 답을 명확히 설명할 수 없다는 것을 깨달았다. 그 이후로 그 답을 찾기 위해서 함수형 프로그래밍에 관한 세가지 정도 책을 읽었었고 그 내용을 이번 포스팅에서 정리해보고자 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tsROJ/btrPtjbXMRn/njwuQWS1kYLyKkzbXFDbj0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tsROJ/btrPtjbXMRn/njwuQWS1kYLyKkzbXFDbj0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tsROJ/btrPtjbXMRn/njwuQWS1kYLyKkzbXFDbj0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtsROJ%2FbtrPtjbXMRn%2FnjwuQWS1kYLyKkzbXFDbj0%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;410&quot; height=&quot;314&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;함수형 프로그래밍이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 그래서 함수형 프로그래밍이란 무엇일까?&lt;br /&gt;&amp;nbsp; 나는 이 질문에 대해 명확하게 대답하는 것 이 상당히 힘들었다. 하지만 찾아보니 나만 그런 것은 아니였던 것 같다. &lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001057504&quot; target=&quot;_self&quot;&gt;&lt;span&gt;함수형 자바스크립트의 저자&lt;/span&gt;&lt;/a&gt;인 마이클 포거스는 &amp;lsquo;클로저 프로그래밍의 즐거움&amp;rsquo;에서 함수형 프로그래밍의 정의에 대해 다음과 같이 이야기했다.&lt;br /&gt;&lt;i&gt;&amp;nbsp; &amp;nbsp;&amp;ldquo;우리도 정답을 모른다. 함수형 프로그래밍은 명확한 정의가 없는 컴퓨팅 용어 중 하나다. 어떤 사람이 어떤 책이, 또는 어떤 언어가 함수형 프로그래밍에 대한 권위를 주장할 수 있겠는가?&amp;rdquo;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&amp;nbsp; &amp;nbsp;&amp;ldquo;함수형 프로그래밍의 실제적인 정의로 함수형 프로그래밍은 애플리케이션 함수의 구성요소, 더 나아가 언어 자체를 함수처럼 여기도록 만들고 이러한 함수 개념을 가장 우선순위에 놓는다. 함수는 값으로 다룰 수 있어서 다른 데이터들과 마찬가지로 저장이 가능하고 전달하거나 리턴 받을 수 있다. 이것이 함수형 프로그래밍에 가장 중요한 개념이다. &lt;b&gt;함수형 사고방식은 문제의 해결방법을 동사(함수)들의 구성으로 접근한다는 것이다&lt;/b&gt;&amp;rdquo;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;내가 여러 책에서 찾아봤던 함수형 프로그래밍에 대한 정의 중에 가장 납득이 잘 갔던 문장은 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;함수형 프로그래밍은 성공적인 프로그래밍, 안전한 프로그램을 위해 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;부수 효과를 최대한 멀리하고 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;조합성을 강조하는 프로그래밍 패러다임이다&lt;/span&gt;.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; 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;/li&gt;
&lt;li&gt;조합성 혹은 모듈화 수준을 높이기 위해서&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;높은 모듈화 수준은 성공적인 프로그래밍의 핵심요소이다.&lt;br /&gt;높은 모듈화 수준은 생산성을 높이고, 오류 없는 함수들의 조합은 프로그램 전체의 안정성을 높여준다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;함수형의 특징(장점)&lt;/b&gt;&lt;/h2&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;  &lt;b&gt;함수형 프로그래밍은 범용적이다.&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 객체지향 프로그래밍에서는 객체가 기준이라면 함수형 프로그래밍에서는 함수가 기준이다.&lt;br /&gt;&amp;nbsp; 객체가 기준이라는 말은 데이터형이 기준이 된다는 의미이고, &lt;b&gt;함수가 기준이라는 말은 로직이 기준이 된다는 의미다.&lt;/b&gt;&lt;br /&gt;객체지향 프로그래밍이 데이터형을 설계한 후 데이터 형에 맞는 메서드를 붙여가는 식이라면, &lt;b&gt;함수형 프로그래밍은 함수로 로직을 설계한 후 로직에 맞는 데이터를 인자로 허용한다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;함수형 프로그래밍은 각기 다르게 생긴 데이터 형을 더 많이 지원하기 위해 함수 내부를 함수로 추상화한다.&lt;/b&gt;&lt;br /&gt;&amp;nbsp; 덕분에 함수 하나가 처리할 수 있는 데이터 형은 끝이 없다. 함수형 프로그래밍에서는 데이터가 user, post,posts 인지는 별로 중요하지 않으며 &lt;b&gt;모두 처리할 수 있는 아주 높은 다형성을 가진 하나의 함수를 만드는 방식으로 프로그래밍을 한다.&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;  &lt;b&gt;함수형 프로그래밍은 코드가 짧다.&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 메서드가 계층 구조의 상속이나 인스턴스 생성으로 기능을 공유해야한다면, 함수가 혼자 존재하기에 아무렇게나 조합하면 된다. 데이터 형에서 자유롭고 조합이 자유로워 재사용성과 조합성이 매우 높다. 함수형 프로그래밍으로 작성된 코드를 보면 데이터의 생김새가 잘 보이지 않는다. &lt;b&gt;추상화의 정도가 높아 데이터 형이 코드에 잘 등장하지 않고 변수명이나 데이터의 구조가 표현되는 일이 적다. 대입문이 적어진다. 이런점들때문에 코드가 매우 짧아진다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;  &lt;b&gt;함수형 프로그래밍은 읽기 쉽다&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 일단 분기가 적고, for while 같은 구조가 잘 등장하지 않아서 코드 모양이 단순하다. 다양한 형을 지원함에도 보조함수의 조합을 통해 분기를 대신하기때문에 if문이 적다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽히는 코드는 읽기 쉽다. 분기 없이 앞으로만 가는 코드는 오류가 발생할 확률도 적고 고치기도 쉽다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;  &lt;b&gt;함수형 프로그래밍은 생명주기가 단순하다.&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 선언과 실행이라는 단순한 생명주기를 갖기 때문에 언제든지 어디서든지 사용하기 쉽다. 함수가 언제 실행되었는가, 어느 스코프에서 선언되었는가, 언제 실행되었는가, 언제 실행될 것인가만 중요하다. 또한 대부분의 함수들은 만든이가 담은 설계, 철학과 같은 추가적인 개념들이 적어서 빨리 이해할 수 있고 쉽게 사용할 수 있다. 누가 만든 함수이든 인자와 결과만으로 가볍게 소통할 수 있다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;함수형 프로그래밍의 키워드&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  일급함수&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;일급은 값으로 다룰 수 있다는 의미로, 아래와 같은 조건을 만족해야한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변수에 담을 수 있다.&lt;/li&gt;
&lt;li&gt;함수나 메서드의 인자로 넘길 수 있다.&lt;/li&gt;
&lt;li&gt;함수나 메서드에서 리턴할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;일급함수란, 아래와 같은 조건을 만족한다는 의미이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수를 변수로 담을 수 있다.&lt;/li&gt;
&lt;li&gt;함수나 메소드의 인자로 함수를 넘길 수 있다.&lt;/li&gt;
&lt;li&gt;함수나 메서드에서 함수를 리턴할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;br /&gt;  고차함수&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; 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;/li&gt;
&lt;li&gt;함수를 리턴하는 함수&lt;/li&gt;
&lt;li&gt;함수를 인자로 받아서 또다른 함수를 리턴하는 함수&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;사실상 함수형 프로그래밍 절반은 고차함수를 적극적으로 활용하는 프로그래밍이라고도 할 수 있다.&lt;br /&gt;유명한 고차함수들로는 map filter reduce 등이다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  순수한 함수&lt;/b&gt;&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;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  참조 투명성?&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 프로그램의 변경 없이 어떤 표현식을 값으로 대체할 수 있다는 뜻이다.&lt;br /&gt;&amp;nbsp; 순수한 함수 f(x)가 y를 반환한다면, f(x)는 y로 대체될 수 있다는 뜻이다.&lt;br /&gt;&amp;nbsp; 같은 입력이 주어지면 항상 같은 결과를 내놓기 때문에 프로그램의 결과를 예측하고 추론할 수 있다. 이처럼 참조 투명성은 개발자나 컴파일러가 평가 결과를 추론할 수 있게 한다. 그래서 프로그램이 실행되기 전에 컴파일러가 코드를 최적화하거나 코드가 평가되는 시점을 늦출 수 있다. 또한 프로그램의 동작을 항상 알 수 있기 때문에 더 안전할 뿐만 아니라 더 쉽게 조합하고 유지 보수하고 변경하고 테스트 할 수 있다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;안전한 프로그래밍?&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 이전 함수형 프로그래밍의 정의에서 이야기했던 &quot;안전한 프로그램&quot;에 대해 조금 더 이야기하고자 한다. 이에 대해서는 &lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001834373&quot; target=&quot;_self&quot;&gt;&lt;span&gt;코틀린을 다루는 기술&lt;/span&gt;&lt;/a&gt; 중에서 내가 동의할 수 있는 부분만 발췌했다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  안전한 프로그래밍이란 무엇인가?&lt;/b&gt;&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;/ul&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  프로그램을 더 안전하게 만드려면?&lt;/b&gt;&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;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그램이 반환하는 결괏값에 덧붙여 프고르램 밖에서 관찰할 수 있는 어떤 변화를 뜻한다.&lt;/li&gt;
&lt;li&gt;값을 반환하는 메소드나 함수가 외부상태를 변경하는 경우&lt;/li&gt;
&lt;li&gt;부수효과&lt;/li&gt;
&lt;/ul&gt;
&lt;/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;예외를 던지는 것은 goto이 현대적 변형이다.&lt;/li&gt;
&lt;li&gt;이로인해 프로그램이 스파게티 코드가 될 수 있다.&lt;/li&gt;
&lt;li&gt;스파게티 코드라는 말은 프로그램 흐름이 어디서 시작하는지 알 수 없지만, 어디로 흘러가는지 제대로 따라갈 수 없다는 뜻이다.&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;그리고 그런 함수 합성이 프로그램을 이루는 전부다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 함수의 합성부분과 예외를 던지지 말라는 부분은 내가 함수형 프로그래밍을 처음 접했을 때는 이해가 되지 않는 부분이였다. 하지만 이제는 조금은 공감할 수 있는 부분이다.&lt;br /&gt;&amp;nbsp; 현재 회사에서 프로덕션 코드를 작성할때는 예외를 냅다 던지는 것보다 Result로 감싸서 사용하고 있다. Result를 사용하게되면, 예외에 대한 처리를 더 부드럽게, 자연스럽게 할 수 있다. 실패했을 때(에러가 났을때) 어디로 어떻게 가서 어떤 처리를 하는지 잘 트래킹할 수 있다. 그냥 코드를 부드럽게 읽으면 나와있다.&lt;br /&gt;&amp;nbsp; 물론 비슷한 처리를 try~ catch가 해줄 수 있지만 이보다 코드가 깔끔하게 나올 수 있다. 이에 관련한 글은 &lt;a href=&quot;https://toss.tech/article/kotlin-result&quot; target=&quot;_self&quot;&gt;&lt;span&gt;토스의 에러핸들링을 다른 클래스에게 위임하기&lt;/span&gt;&lt;/a&gt; 라는 블로그 글을 보면 잘 설명되어있다. Result에 적응하고 나니, throw Error를 하는데 어디서 잡아주는 지가 내 눈에 바로 보이지 않으면, 잘 처리하다가 갑자기 쓰레기통에 던져버리는 기분이 들었다. 물론 적응하기 까지는 오래걸렸다. 처음에는 Reuslt를 써서 오히려 코드가 더 어려워지는 느낌을 상당히 많이받았다. 하지만 Result를 잘 사용할 수있는 활용성(추상화 수준)이 높은 함수들이 만들어지고서야 좀 제대로 사용할 수 있었다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;try &amp;hellip; catch를 사용했을때&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;return runCatching {
  loginApiClient.login(request)
}.onFailure { e -&amp;gt; 
  if (e.errorCode != &quot;INVALID_PASSWORD&quot;) throw e
}.getOrNull()
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;runCatching을 사용했을 때&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;try {
  loginApiClient.login(request) 	
} catch (e: LoginException) {
  if (e.errorCode == &quot;INVALID_PASSWORD&quot;) {
    return null
  } else {
    throw e
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp; 합성할 수 있는 함수를 만들기 위해서는 높은 수준으로 추상화를 해야한다. 그러다보면 저절로 작게 쪼개지게 된다. 당장은 그 함수에서 하는일이 딱히 없어보일 수 있다. 하지만 계속 사용하다보면 재사용성이 높은 함수가 되어있다는 것을 조금씩 깨닫게된다. 하나의 함수를 만들 때 처음부터 짜는 것이 아니고, 함수에 함수를 조립해서 또하나의 함수를 만들고, 그렇게 함수들을 조립하다보면 하나의 기능이 되어있다는 것을 알 수 있었다. (물론 항상 그런 것은 아니다.) 이런 식으로 한번 코드를 짜보면 약간 코드짜는게 퍼즐 맞추는 것 마냥, 레고 조립하는 것 마냥 재밌다는 느낌을 받을 때가 있다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;마무리&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 함수형 프로그래밍의 장점에 대해서 포스팅을 썼지만, 함수형 프로그래밍이 객체지향 프로그래밍보다 우월하다고 생각하는 것이 아니다. 함수형 프로그래밍과 객체지향 프로그래밍은 상호 배제적인 관계가 아니다. 결국 두가지 패러다임 모두 유연하고 지속 가능한 시스템을 만들자는 목적이며, 서로를 보완해줄 수 있는 관계라고 생각한다. 나에게 있어서 함수형 프로그래밍을 사용해보면 얻었던 가장 큰 이득(장점)은, 함수를 더 쪼개고 재사용성 가능하게 만들어보려고 노력했었던 경험과 객체 지향 패러다임이 유일하며 가장 좋은 패러다임이라는 생각에서 벗어날 수 있었다는 것이라고 생각한다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Ref.&lt;br /&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001834373&quot; target=&quot;_self&quot;&gt;&lt;span&gt;코틀린을 다루는 기술&lt;/span&gt;&lt;/a&gt; &lt;br /&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001057504&quot; target=&quot;_self&quot;&gt;&lt;span&gt;함수형 자바스크립트&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001033088&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;코틀린으로 배우는 함수형 프로그래밍&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>디버깅중  </category>
      <author>mangdo</author>
      <guid isPermaLink="true">https://doing7.tistory.com/159</guid>
      <comments>https://doing7.tistory.com/159#entry159comment</comments>
      <pubDate>Tue, 25 Oct 2022 01:00:16 +0900</pubDate>
    </item>
    <item>
      <title>[책리뷰] 오늘부터 IT를 시작합니다</title>
      <link>https://doing7.tistory.com/158</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-08-21 오후 8.45.44.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IZzhl/btrKbtcWF84/7IkxK6glnUenyHXkdgafdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IZzhl/btrKbtcWF84/7IkxK6glnUenyHXkdgafdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IZzhl/btrKbtcWF84/7IkxK6glnUenyHXkdgafdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIZzhl%2FbtrKbtcWF84%2F7IkxK6glnUenyHXkdgafdk%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;313&quot; height=&quot;250&quot; data-filename=&quot;스크린샷 2022-08-21 오후 8.45.44.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아는 것과 가르치는 것은 다르다는 말은 많이 들어보았을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직장에서 일하게되면서 개발자 뿐만아니라 비개발자들과도 소통해야하는 상황을 마주할 일이 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 때마다 느끼는 것은 안다는 것과 설명하는 것은 다른 것이구나.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 이해하는 것을 넘어서 쉬운말로 풀어서 남을 이해시키는 것이 훨씬 어려운 부분이구나.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 남을 이해시키려고 하다보면, &lt;br /&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;그래서 나는 IT 입문자를 위한 책을 보는 것을 꽤나 좋아한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;술술 넘어가서 기분 좋은 것도 있고, 쉽게 읽다가도 은근 얻어가는 것들이 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책역시 그런 부류의 책이였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[ 책 소개 &amp;amp; 추천 독자 ]&lt;/b&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-filename=&quot;스크린샷 2022-08-21 오후 9.39.21.png&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;1068&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAIpGY/btrJ9vClaqg/v9GEM2NY2UxCfd9m0YICNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAIpGY/btrJ9vClaqg/v9GEM2NY2UxCfd9m0YICNK/img.png&quot; data-alt=&quot;얇다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAIpGY/btrJ9vClaqg/v9GEM2NY2UxCfd9m0YICNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAIpGY%2FbtrJ9vClaqg%2Fv9GEM2NY2UxCfd9m0YICNK%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;426&quot; height=&quot;400&quot; data-filename=&quot;스크린샷 2022-08-21 오후 9.39.21.png&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;1068&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;그러면서도 IT 전반에 걸친 이야기를 하고 있다. 넓고 얉은 지식&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;IT 버전&lt;/span&gt;&lt;/span&gt;을 읽고 있는 느낌을 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념들을 쉬운 비유를 들어서 설명하고 있기때문에 이야기 책마냥 술술 넘어가고 책 자체도 얇고 가벼워서 나는 출퇴근 시간에 가볍게 읽었었다. 사실 나같은 경우에는(1년차 개발자) 이미 거의 알고 있는 내용들이라서 따로 시간을 내서 읽을 정도의 책은 아니였다. 그래도 책자체는 굉장히 흥미롭게 읽었다. 추천 독자는 크게 두분류지 않을까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 이제 막 컴공에 들어간 신입생..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 내가 아무것도 몰랐던 1학년?2학년 시절에 읽었으면 도움이 되었을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. IT 분야에서 종사하는 비개발자 분들&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;이외에도 나같이 개발자지만, 비전공자들에게 IT 개념을 쉽게 설명해주고 싶은 사람들한테도 괜찮을 것 같은 책이다. 일단 두껍지 않아서 쉽게 시도해볼 수 있는 책인것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[ 인상깊었던 부분 ]&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 쉬운 비유&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1tZGo/btrKgtbYlll/kzbVovEXz9aGnvUTYKk5Zk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1tZGo/btrKgtbYlll/kzbVovEXz9aGnvUTYKk5Zk/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot; data-filename=&quot;KakaoTalk_Photo_2022-08-21-21-39-35 002.jpeg&quot; width=&quot;270&quot; height=&quot;270&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1tZGo/btrKgtbYlll/kzbVovEXz9aGnvUTYKk5Zk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1tZGo%2FbtrKgtbYlll%2FkzbVovEXz9aGnvUTYKk5Zk%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;1440&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjWL6r/btrKay6Ggjl/cnKkway1Cg0MmlTi3xOEG1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjWL6r/btrKay6Ggjl/cnKkway1Cg0MmlTi3xOEG1/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot; data-filename=&quot;KakaoTalk_Photo_2022-08-21-21-39-35 003.jpeg&quot; width=&quot;198&quot; height=&quot;198&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjWL6r/btrKay6Ggjl/cnKkway1Cg0MmlTi3xOEG1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjWL6r%2FbtrKay6Ggjl%2FcnKkway1Cg0MmlTi3xOEG1%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;1440&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OaezJ/btrKcvPaSiP/646xjuGBCggFSYKqJulshK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OaezJ/btrKcvPaSiP/646xjuGBCggFSYKqJulshK/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot; data-filename=&quot;KakaoTalk_Photo_2022-08-21-21-39-36 004.jpeg&quot; width=&quot;446&quot; height=&quot;446&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OaezJ/btrKcvPaSiP/646xjuGBCggFSYKqJulshK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOaezJ%2FbtrKcvPaSiP%2F646xjuGBCggFSYKqJulshK%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;1440&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴공 교수님의 딱딱하고 오래된 예시들 말고, 비교적 문학적이고 최신의 것의 비유들이 나온다.(ㅋㅋㅋㅋ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 나한테는 그런 비유들이 신선하고 재미있는 요소였다.&lt;/p&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; 다양한 언어를 사용하는 전세계 사람들이 서로 소통하기 위해 영어라는 공용어를 지정했지만, 이것보다 더 보편화된 공용어가 있다. body Language. 이 방식은 말도 글자도 통하지 않는 곳에서도 유일하게 의사소통이 가능하다. 이 방식의 가장 큰 특징은 직관적이라는 것이다. java, c++, javascript 등 다양한 프로그래밍 언어 사이에서도 &lt;span&gt;바디 랭귀지처럼&lt;/span&gt;서로의 의도를 파악할 수 있는 직관적인 JSON이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 개발자로써 인상깊었던 부분 : MySQL에 이어 MariaDB를 만든 몬티&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이 부분은 그냥 순전히 내가 기억하고 싶어서 적는 부분이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;nbsp; 돌고래(MySQL)와 물개(MariaDB)의 경쟁은 서로를 자극하였고 더 좋은 관계형 데이터베이스를 탄생시켰습니다. MySQL은 오라클이라는 서비스를 기대할 수 있으며, MariaDB는 몬티 재단이 이끄는 최고 개발자 사단의 능력으로 성능 좋은 데이터베이스를 사용할 수 있습니다. 현재 무료관계형 데이터베이스의 저울은 MariaDB로 많이 이동하고 있습니다. 대표적인 레드햇 운영체제인 페도라는 MySQL을 버리고 19버전 부터 MariaDB를 채택하였습니다. 이러한 이동은 거대한 기업을 중심으로 연속적으로 일어나고 있습니다.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;결론적으로는 돌고래와 물개의 싸움으로 인해 데이터베이스는 발전하고 성장하였습니다. 몬티가 가진 철학은 사라질 위기에 처했던 무료 데이터베이스 시장의 불씨를 되살렸습니다. 개발자의 가장 멋진 모습 중 하나가 바로 이러한 철학을 가진 자의 용기가 아닐까 생각합니다. 지금도 관계형 데이터베이스의 전쟁은 계속되고 있습니다. 이 전쟁으로 인해 저는 오랜동안 코딩을 하면서 당장 눈앞에 프로젝트만을 바라보고 살아온 것은 아닌지 생각하게되었습니다. 몬티처럼 IT라는 거대한 생태계에 혁신이라는 전쟁을 일으킬수 있는 꿈을 꾸고 싶다는 생각을 해봅니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;+) 기술의 비하인드같은 내용&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;289&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK2MyC/btrKatp88fM/SWle2drfhzhsk2sLszNwKK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK2MyC/btrKatp88fM/SWle2drfhzhsk2sLszNwKK/img.jpg&quot; data-alt=&quot; Tux&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK2MyC/btrKatp88fM/SWle2drfhzhsk2sLszNwKK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK2MyC%2FbtrKatp88fM%2FSWle2drfhzhsk2sLszNwKK%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;186&quot; height=&quot;225&quot; data-origin-width=&quot;289&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; Tux&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;리눅스의 마스코트인 턱스를 만들때 리누스가 &quot;맛있는 청어를 배불리 먹고 트림하는 살찐 펭귄이여야 할것, 배가 너무 불러서 일어나지 못하고 안자있는 사랑스러운 펭귄이여야할 것&quot;이라고 요청했다고 한다. 펭귄으로 정한 이유는 리누스가 리눅스 소개를 위해 유닉스 모임을 가던 중 작은 펭귄한테 물리게 되었고 그 일로 인해 펭귄을 좋아하게되면서 리눅스의 로고로 떠올리게 되었다고 한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뜬금없지만 너무 귀여움....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IT에 대한 넓고 얇은 지식을 가지고 싶은 사람들에게&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가볍게 읽을 만한 책으로 추천한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(하지만 이책을 통해 많은 것을 얻어갈 것이라는 기대는 하지않았으면 좋겠다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;&quot;한빛미디어 &amp;lt;나는 리뷰어다&amp;gt; 활동을 위해서 책을 제공받아 작성된 서평입니다.&quot;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;</description>
      <category>디버깅중  </category>
      <author>mangdo</author>
      <guid isPermaLink="true">https://doing7.tistory.com/158</guid>
      <comments>https://doing7.tistory.com/158#entry158comment</comments>
      <pubDate>Sun, 21 Aug 2022 21:40:54 +0900</pubDate>
    </item>
    <item>
      <title>[책리뷰] 헤드퍼스트 디자인패턴 개정판</title>
      <link>https://doing7.tistory.com/157</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;569&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZV9Gj/btrzzpstFTP/dpPyJ2q4DwqAxMVLr00oV1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZV9Gj/btrzzpstFTP/dpPyJ2q4DwqAxMVLr00oV1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZV9Gj/btrzzpstFTP/dpPyJ2q4DwqAxMVLr00oV1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZV9Gj%2FbtrzzpstFTP%2FdpPyJ2q4DwqAxMVLr00oV1%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;376&quot; height=&quot;268&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;569&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;현업에서 개발을 하다보면 느끼겠지만, 기획은 계속 변한다. 어제까지 분명 A라고 했는데 아침 회의에 갑자기 B도 아니고 C로 변해있는 경우도 있고, 열심히 다 만들었더라도 유저들이 다른 것을 요구하거나 새로운 기능을 원할 수 있다. 흔히 있는 일이고 사실&amp;nbsp;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;요구사항 변경은 SW의 본질이라고 할 수 있다. 그렇기에 변경 용이한 아키텍처를 작성하는 것이 개발자에게는 중요한 일일 것이다. &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[ 책 소개 ]&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OqQpH/btrzBrCxciZ/Dwg6EixU5ziznhDJNkooNk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OqQpH/btrzBrCxciZ/Dwg6EixU5ziznhDJNkooNk/img.jpg&quot; width=&quot;326&quot; height=&quot;326&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot; data-filename=&quot;KakaoTalk_Photo_2022-04-17-18-25-33.jpeg&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OqQpH/btrzBrCxciZ/Dwg6EixU5ziznhDJNkooNk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOqQpH%2FbtrzBrCxciZ%2FDwg6EixU5ziznhDJNkooNk%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;1440&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcbFmD/btrzwgbrtcJ/qQDTbNOUkWdhR1WBzyEEvK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcbFmD/btrzwgbrtcJ/qQDTbNOUkWdhR1WBzyEEvK/img.jpg&quot; width=&quot;343&quot; height=&quot;343&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot; data-filename=&quot;KakaoTalk_Photo_2022-04-17-19-26-42.jpeg&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcbFmD/btrzwgbrtcJ/qQDTbNOUkWdhR1WBzyEEvK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcbFmD%2FbtrzwgbrtcJ%2FqQDTbNOUkWdhR1WBzyEEvK%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;1440&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;재미없다고 돌려까는 건가..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 제목에서 알 수 있듯이, 디자인 패턴 설명하고 있는 책이다. 다 다루는 것은 아니다. 실무에서 자주 사용하는 디자인 패턴들을 집중해서 다루고 있다. (14가지 패턴 : 전략패턴, 옵저버 패턴, 데코레이터 패턴, 팩토리 패턴, 싱글턴 패턴, 커맨트 패턴, 어댑터 패턴, 퍼사드 패턴, 템플릿 메소드 패턴, 반복자 패턴, 컴포지트 패턴, 상태 패턴, 프록시 패턴, 복합 패턴) 그 면이 오히려 좋았다. 책은 총 654페이지로 굉장히 두꺼운데, 그림이 대다수를 차지하고 있다. 일부로 가독성+학습을 위해서 책의 한페이지를 글로 꽉꽉 채우지 않은 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 책이 IT 책치고는 굉장히 친절한 편이다. 책 쓰신 분이 뇌 과학에 관심이 많은 듯했다. 어찌되었든 이 책 앞단에서 말하는 것에 따르면, 두뇌는 사물보다는 사람에게 더 많은 관심을 가지기 때문에 책에서도 여러 사람이 등장하고, 사적인 대화체를 많이 사용했다고한다. 읽을 때보다 어떤 일을 실제로 할때 더 잘 배우고 더 잘 기억하기 때문에 연습문제도 많이 배치했고 양쪽 두뇌를 모두 사용할 수 있는 내용을 담았다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 그냥 여러 디테일을 신경쓰면서 열심히 만들으셨다고 한다. 덕분에 디자인 패턴책은 전공책스럽겠지라는 편견을 깨버리고, 의외로 재밌었다.. (돈받아서 해주는 말이 아니고, 어짜피 돈은 안받았다. 책만 받았다) 주말에 몇시간 정도밖에 안봤지만, 벌써 150쪽까지 봤다. 심지어 남는 내용은 그 이상이라서 뒷부분이 궁금해지더라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[ 책에서 인상 깊었던 파트: 옵저버 패턴 ]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 실제로 회사에서 쓰고 있는 패턴이기도 해서 집중해서 본 파트다. 책에 대해 두루뭉실하게 계속 설명하는 것보다, 이 파트에서 대충 어떤 흐름을 가지고 있는 지 설명하면 이 책에 대한 더 정확한 설명이 되리라고 생각한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2022-04-17-19-44-28.jpeg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vY3Gd/btrzAWpP4M2/2slc0rS44q04IOJSenZHYK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vY3Gd/btrzAWpP4M2/2slc0rS44q04IOJSenZHYK/img.jpg&quot; data-alt=&quot;옵저버 패턴 예제&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vY3Gd/btrzAWpP4M2/2slc0rS44q04IOJSenZHYK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvY3Gd%2FbtrzAWpP4M2%2F2slc0rS44q04IOJSenZHYK%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;420&quot; height=&quot;420&quot; data-filename=&quot;KakaoTalk_Photo_2022-04-17-19-44-28.jpeg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&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;b&gt;1. 기상 모니터링 애플리케이션을 만드는 예제 + 요구사항 분석&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp;&lt;/b&gt;책에서 주어진 예제의 요구사항을 간단하게 설명하면 다음과 같다. &quot;WeatherData 객체에서 현재 기상 조건을 측정할때마다, 디스플레이에 최신 기상 데이터들로 갱신해라&quot; 추가적으로 고려해야하는 사항은 &quot;나중에 다른 기상 조건(풍속)이 추가될 수도 있으며, 다른 디스플레이가 추가될 수 있다&quot;라는 점이다. 확장성을 생각하고 설계해야하는 상황이며, 이를 위해서 옵저버 패턴을 적용해보고자 한다.&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_2022-04-17-19-56-55.jpeg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d3gevy/btrzzplLffi/sJFTdGO0ejR8WrS6Ts8hg0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d3gevy/btrzzplLffi/sJFTdGO0ejR8WrS6Ts8hg0/img.jpg&quot; data-alt=&quot;옵저버 패턴 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d3gevy/btrzzplLffi/sJFTdGO0ejR8WrS6Ts8hg0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd3gevy%2FbtrzzplLffi%2FsJFTdGO0ejR8WrS6Ts8hg0%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;372&quot; height=&quot;372&quot; data-filename=&quot;KakaoTalk_Photo_2022-04-17-19-56-55.jpeg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&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;b&gt;2. 옵저버 패턴 설명&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 옵저버 패턴은 신문구독과 흡사하다. 신문사를 주제(subject)라고 하고 구독자를 observer라고 하자. 독자가 신문을 구독하면, 새로운 신문이 나올 때마다 배달을 받는다. 옵저버 패턴은 이와 굉장히 흡사하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1) Subject에 객체를 옵저버로 구독한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2) 옵저버로 등록되면, Subject의 데이터가 바뀔때마다 갱신 내용을 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;3) 옵저버에서 나가게 되면, 더이상 갱신내용을 전달 받지 않는다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;옵저버 패턴이란?&lt;br /&gt;한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에 연락이가고 자동으로 내용이 갱신이 되는 방식으로 일대다(one-to-many) 의존성을 정의한다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2022-04-17-20-02-50.jpeg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKq0ob/btrzyWKWM9b/dPck0OH2JBwTP8WnUNErl1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKq0ob/btrzyWKWM9b/dPck0OH2JBwTP8WnUNErl1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKq0ob/btrzyWKWM9b/dPck0OH2JBwTP8WnUNErl1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKq0ob%2FbtrzyWKWM9b%2FdPck0OH2JBwTP8WnUNErl1%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;377&quot; height=&quot;377&quot; data-filename=&quot;KakaoTalk_Photo_2022-04-17-20-02-50.jpeg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;3. 옵저버 패턴 구조&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 옵저버 패턴은 여러가지 방법으로 구현할 수 있겠지만, 보통은 Subject 인터페이스와 옵저버 인터페이스가 들어있는 클래스 디자인으로 구현한다. Observer 인터페이스만 구현한다면 무엇이든 옵저버 클래스가 될 수 있다. 각 옵저버는 특정&amp;nbsp;&lt;span&gt;Subject의 registerObserver()를 통해서 등록 할 수 있다.&lt;span&gt; Subject 역할을 하는 구상 클래스에는 항상 Subject 인터페이스를 구현해야한다. 옵저버를 등록하는registerObserver()와 옵저버 등록을 취하는 removeObserver(), 상태가 바뀔때마다 모든 옵저버에게 연락하는 notifiyObservers()가 있다.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;* &lt;span&gt;Publish-Subscribe&lt;span&gt; vs 옵저버 패턴&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; 연관은 있지만 같은 패턴은 아니다. &lt;/span&gt;&lt;/span&gt;&lt;span&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;&lt;b&gt;&lt;span&gt;4. 옵저버 패턴를 구현하여 예제를 해결한 코드&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- Subject를 구현한 WheaterData 클래스&lt;/b&gt;&lt;br /&gt;&amp;nbsp; : 기상을 측정하고, 측정할때마다 옵저버인 디스플레이들에게 최신 데이터들을 알려준다.&amp;nbsp;&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;a class=&quot;btn-toggle-moreless&quot;&gt;코드보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;script src=&quot;https://gist.github.com/luna-liner/bee599a6b9e366b87343286fb741fc6e.js&quot;&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 옵저버를 구현한 Display 클래스&lt;/b&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;a class=&quot;btn-toggle-moreless&quot;&gt;코드보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;script src=&quot;https://gist.github.com/luna-liner/8ffc9c7a9c856ffedcd4f1ec02797425.js&quot;&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 테스트&lt;/b&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;a class=&quot;btn-toggle-moreless&quot;&gt;코드보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;script src=&quot;https://gist.github.com/luna-liner/f3a3ed9e0c14472514e933ee2e7bffcc.js&quot;&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;5. 중간중간 등장하는 대화&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ubnOY/btrzBJRnGjf/0Q4meKLwmK2TcFu3G3UzHK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ubnOY/btrzBJRnGjf/0Q4meKLwmK2TcFu3G3UzHK/img.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot; data-filename=&quot;KakaoTalk_Photo_2022-04-17-20-14-30.jpeg&quot; width=&quot;394&quot; height=&quot;394&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ubnOY/btrzBJRnGjf/0Q4meKLwmK2TcFu3G3UzHK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FubnOY%2FbtrzBJRnGjf%2F0Q4meKLwmK2TcFu3G3UzHK%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;1440&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UUhMX/btrzEBrxWfN/O4kt2w1n5SbExsGIWpaB41/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UUhMX/btrzEBrxWfN/O4kt2w1n5SbExsGIWpaB41/img.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot; data-filename=&quot;KakaoTalk_Photo_2022-04-17-20-14-24.jpeg&quot; width=&quot;379&quot; height=&quot;379&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UUhMX/btrzEBrxWfN/O4kt2w1n5SbExsGIWpaB41/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUUhMX%2FbtrzEBrxWfN%2FO4kt2w1n5SbExsGIWpaB41%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;1440&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjaLgb/btrzDuzptsz/M5oabsOYprSYkgWOfVMxu1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjaLgb/btrzDuzptsz/M5oabsOYprSYkgWOfVMxu1/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&quot; data-filename=&quot;KakaoTalk_Photo_2022-04-17-20-14-18.jpeg&quot; width=&quot;384&quot; height=&quot;384&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjaLgb/btrzDuzptsz/M5oabsOYprSYkgWOfVMxu1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjaLgb%2FbtrzDuzptsz%2FM5oabsOYprSYkgWOfVMxu1%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;1440&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/div&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;[ 추천 독자 ]&lt;/span&gt;&lt;/b&gt;&lt;/h3&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;1440&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/23m9N/btrzyYu6FMd/AQ9WEu7QKW0xQzQMxaxif1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/23m9N/btrzyYu6FMd/AQ9WEu7QKW0xQzQMxaxif1/img.jpg&quot; data-alt=&quot;아니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/23m9N/btrzyYu6FMd/AQ9WEu7QKW0xQzQMxaxif1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F23m9N%2FbtrzyYu6FMd%2FAQ9WEu7QKW0xQzQMxaxif1%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;389&quot; height=&quot;389&quot; data-filename=&quot;마케팅.jpeg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1440&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;b&gt;&lt;span&gt;1. &lt;/span&gt;&lt;span&gt;자바를 잘하진 않더라도, 어느정도는 아는 사람&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;예제 코드가 모두 자바로 되어있어서 자바를 아예 모른다면 어려울 것 같다. 그림이 워낙 많은 편이라서 &quot;자바를 모르는 사람도 읽을 수있다!&quot; 이런 의미인것 같은데, 예제코드가 워낙 좋아서 보고 코드를 보고 이해할 수 있는 사람이 보는 것이 좋을 것 같다.&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&gt;2.&lt;span&gt;&amp;nbsp;디자인 패턴 입문자&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; 나 역시 디자인 패턴을 따로 공부해본 적은 없었다. 이름을 듣긴 들었지만 뭐하나 제대로 이해한 패턴은 없었다. 근데도&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;b&gt;&lt;span&gt;&lt;span&gt;3. 객체 지향을 조금 더 잘 활용해보고 싶은 사람&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; 객체지향의 특징이라면서 달달 외워댄 &quot;추상화, 캠슐화, 다형성, 상속&quot;을 어떻게 하면 더 잘 적용할 수 있을지에 대해 다루고 있다. 개인적으로 나는 코드 재사용을 막기위해서 상속을 써왔었는데, 그러니까 생각지 못한 곳에 변화가 가해지는 문제가 있었다. 그래서 최근에 내린 결론은 &quot;그렇게 버그를 낼 바에는 코드 몇줄 더 쓰겠다. 안전한 코드가 먼저다.&quot; 라는 생각이였다. 그렇게 반복된 코드를 짤때마다 약간 자괴감이 들고있었는데, 그런 상황들에서 적용해볼 수 있는 해법들을 제안해줘서 좋았다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1000.png&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2i4MC/btrzFFG9arl/qzckXm5XFcgOWQgcztPoTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2i4MC/btrzFFG9arl/qzckXm5XFcgOWQgcztPoTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2i4MC/btrzFFG9arl/qzckXm5XFcgOWQgcztPoTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2i4MC%2FbtrzFFG9arl%2FqzckXm5XFcgOWQgcztPoTK%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;360&quot; height=&quot;360&quot; data-filename=&quot;1000.png&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 운이 좋게도 내가 딱 추천 독자에 맞는 사람이였고, 그런 의미에서 개인적으로 정말 재밌게 본 책이다. 솔직히 아직 다 보진 못했고 14가지 패턴중에서 데코레이터 패턴까지만 본 상태이다. 지금까지는 굉장히 만족스럽고 공감이 많이 갔다. 한빛미디어에서 책받아서 해주는 말은 아니고 찐심임..ㅎ&lt;/p&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&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;&quot;한빛미디어 &amp;lt;나는 리뷰어다&amp;gt; 활동을 위해서 책을 제공받아 작성된 서평입니다.&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 TIP</category>
      <author>mangdo</author>
      <guid isPermaLink="true">https://doing7.tistory.com/157</guid>
      <comments>https://doing7.tistory.com/157#entry157comment</comments>
      <pubDate>Sun, 17 Apr 2022 20:34:35 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] 프로퍼티 위임 : 일반적인 프로퍼티 패턴을 구현하는 방법</title>
      <link>https://doing7.tistory.com/156</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 코틀린의 프로퍼티 위임(Delegation)을 이용하면 일반적인 프로퍼티 패턴들을 간단하게 사용할 수 있다. 대표적인 예시로 표준 라이브러리의 lazy(처음 사용하는 요청이 들어올 때 초기화하는 패턴), observable(변화가 있을 때 감지하는 패턴)와 의존성 주입, 데이터 바인딩 등에 활용될 수 있다. 일반적으로 이런 패턴들을 사용할 때 자바에서는 어노테이션들을 많이 활용해야 한다. 하지만 코틀린에서는 프로퍼티 위임을 사용해서 간단하고 type-safe하게 구현할 수 있다. 우선 프로퍼티 위임에 대해서 간단히 알아보고, 동작 원리, 어떻게 활용될 수 있는지에 대해서 차례대로 알아보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cp9ky6/btrxWBuIUNO/qrSWXr3c3WtWP5Y6KODYC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cp9ky6/btrxWBuIUNO/qrSWXr3c3WtWP5Y6KODYC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cp9ky6/btrxWBuIUNO/qrSWXr3c3WtWP5Y6KODYC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcp9ky6%2FbtrxWBuIUNO%2FqrSWXr3c3WtWP5Y6KODYC0%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;480&quot; height=&quot;240&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✏️&amp;nbsp; 프로퍼티 위임(Delegation)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; 프로퍼티 위임은 프로퍼티의 접근자(getter, setter) 구현을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;다른 객체에게 맡길 수 있는 문법이다.&lt;/span&gt; 즉, 다른 객체의 메서드를 활용해서 프로퍼티의 접근자(getter, setter)를 만들 수 있다. 반복적으로 사용되는 프로퍼티 행위를 추출해서 재사용함으로써 코드를 간결하게 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 예를 들어서, Int 타입의 프로퍼티에 음수가 저장되는 것을 방지하는 Setter를 프로젝트 내에서 반복적으로 사용하고 싶을 수 있다. 이러한 요구사항을 가진 프로퍼티마다 Setter를 일일이 정의하는 것은 너무 번거로울 수 있다. 따라서 이러한 프로퍼티 행위를 추출하여 OnlyPositive라는 객체를 만들자. 이때 메서드 이름이 &lt;span&gt;중요하다. Getter는 getValue, Setter는 setValue 함수를 사용해서 만들어야한다. 이후&amp;nbsp;&lt;/span&gt;프로퍼티 선언문 뒤에 &quot;by 객체&quot;를 적으면 해당 객체가 프로퍼티의 Getter, Setter를 대리 구현하게 된다.&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;script src=&quot;https://gist.github.com/luna-liner/2a307e7764079e5098f91434f163f774.js&quot;&gt;&lt;/script&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;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;✏️&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;프로퍼티 위임의 동작방식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 프로퍼티 위임이 어떻게 동작하는지 이해하기 위해서는 by가 어떻게 컴파일되는지 코드를 보는 것이 가장 명확한 방법일 것 이다. 위의 코드는 다음과 비슷한 형태로 컴파일이 된다. (프로퍼티가 top-level에서 사용될 때에는 this 대신 null로 바뀐다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script src=&quot;https://gist.github.com/luna-liner/96c70ce5bfeffbb6a65c5229ca831fa4.js&quot;&gt;&lt;/script&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; 코드에서 알 수 있는 것처럼 getValue와 setValue는 단순하게 값만 처리하게 바뀌는 것이 아니라, 컨텍스트(this)와 프로퍼티 레퍼런스의 경계도 같이 사용하는 형태로 바뀌게 된다 프로퍼티에 대한 레퍼런스는 이름, 어노테이션과 관련된 정보등을 얻을 때 사용된다. 그리고 &lt;b&gt;컨텍스트는 함수가 어떤 위치에서 사용하되는지와 관련된 정보를 제공&lt;/b&gt;해준다. 이러한 정보로 인해서 g&lt;b&gt;etValue()와 setValue() 메서드가 여러개 있어도 컨텍스트를 활용함으로, 상황에 따라서 적절한 메서드가 선택&lt;/b&gt;된다. 객체를 프로퍼티에 위임하려면 val는 getValue 연산, var의 경우 getValue와 setValue연산이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 이러한 연산은 지금까지 살펴본 것처럼 멤버 함수로도 만들 수 있지만, 확장함수로도 만들 수 있다. 예를 들어 다음 코드는 코틀린의 stdlib안에 Map&amp;lt;&amp;gt;.getValue 확장 함수가 정의되어 있기 때문에 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;✏️&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;프로퍼티 위임의 활용 예시&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 코틀린의 stdlib에서 프로퍼티 델리게이트들이다. 범용적으로 사용되는 패턴들에 대한 프로퍼티 델리게이터임으로 알아두면 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- lazy&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Delgates.observable : 변화가 있을 때 감지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Delgates.vetoable&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Delgates.notNull&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 프로퍼티 델리게이트는 프로퍼티와 관련된 다양한 조작을 할 수 있으며, 컨텍스트와 관련된 대부분의 정보를 갖는다. 이러한 특징으로 인해서 다양한 프로퍼티의 동작을 추출해서 재사용할 수 있다. 표준 라이브러리의 lazy와 observable이 이에 대한 대표적 예시이다. 프로퍼티 위임은 프로퍼티 패턴을 추출하는 일반적인 방법으로 많이 사용되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script src=&quot;https://gist.github.com/luna-liner/3ce9be9dcadf94ce790f3774db5aafb1.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Reference :&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;초보자를 위한 코틀린 200제(엄민석 저)&lt;/p&gt;</description>
      <category>Java &amp;amp; Kotlin</category>
      <author>mangdo</author>
      <guid isPermaLink="true">https://doing7.tistory.com/156</guid>
      <comments>https://doing7.tistory.com/156#entry156comment</comments>
      <pubDate>Wed, 30 Mar 2022 16:07:45 +0900</pubDate>
    </item>
  </channel>
</rss>