<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ziggy_stardust.log</title>
        <link>https://velog.io/</link>
        <description>unagi.zoso == ziggy stardust == devswansong</description>
        <lastBuildDate>Mon, 17 Nov 2025 18:26:40 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. ziggy_stardust.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ziggy_stardust" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[제프 베조스 '변하지 않는 것' - 영어 회화 공부 (2)]]></title>
            <link>https://velog.io/@ziggy_stardust/%EC%A0%9C%ED%94%84-%EB%B2%A0%EC%A1%B0%EC%8A%A4-%EB%B3%80%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EA%B2%83-%EC%98%81%EC%96%B4-%ED%9A%8C%ED%99%94-%EA%B3%B5%EB%B6%80-2</link>
            <guid>https://velog.io/@ziggy_stardust/%EC%A0%9C%ED%94%84-%EB%B2%A0%EC%A1%B0%EC%8A%A4-%EB%B3%80%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EA%B2%83-%EC%98%81%EC%96%B4-%ED%9A%8C%ED%99%94-%EA%B3%B5%EB%B6%80-2</guid>
            <pubDate>Mon, 17 Nov 2025 18:26:40 GMT</pubDate>
            <description><![CDATA[<p>H : 
Yeah, I think one of things I&#39;ve learned from you and reading what you said and just being in your presence is you think about things very differently sometimes than everybody else does.
네.. 제가 당신의 글을 읽고 당신과 함께 있으면서 배웠던 점은 당신은 가끔 다른 사람들과 아주 다르게 생각한다는 점입니다.</p>
<p>Sort of this first order thinking that you have. And so often times people in a sort of disruptive mordern day world are thinking about how do you predict the future? And what you said was diametrically opposite. 
일종의 근본적인 사고 같은 것이죠. 요즘같이 급변하는 현대 사회에 사람들은 종종 &#39;어떻게 미래를 예측할까?&#39; 를 고민한다. 그리고 당신이 말한 것은 그와 완전히 정반대였습니다. </p>
<p>You said what are the things about the future that we know are not going to change and then you gave examples.
당신은 미래에 대해 우리가 변하지 않을 것이라고 알고 있는 것은 무엇인가? 라고 물었고 예시들을 주셨죠.</p>
<p>JB :
You said, you know, uh, we&#39;re not going to want to get packages slower. We&#39;re not going to get we&#39;re not going to want things with less quality and we&#39;re not going to want to pay more for those things and you can build a business around knowing the things that aren&#39;t going to change. Tell us a little bit about that thinking.
&#39;우리는 택배를 더 늦게 받고 싶어하지 않을 겁니다. 품질이 더 나쁜 물건을 원하지도 않을 거고요. 그리고 그런 물건에 돈을 더 내고 싶어 하지도 않을 겁니다.&#39; 라고 말이죠. 그리고 이렇게 변하지 않을 것들을 파악해 비즈니스를 구축할 수 있다고 했습니다.
그 생각에 대해 좀 더 말씀해 주시겠나요?</p>
<p>So you know we live in a very dynamic world and everything changes. technologies change that your competitive set changes, so many things are changing every day.
아시다시피 우리는 매우 역동적인 세상에 살고 모든 것이 변합니다. 기술이 바뀌며 경쟁 구조도 변하고 많은것들이 매일 바뀝니다.</p>
<p>You can&#39;t build a strategy on those. You have to build a strategy around stability. So you have to find the things that are not going to change and say 10 years from now what&#39;s going to be the same
그런 것들 위에선 전략을 세울수 없습니다. 전략은 안정성을 중심으로 세워야 합니다. 그래서 변하지 않을 것을 찾고 10년 뒤에도 같을 것은 무엇인가 자문해야합니다.</p>
<p>Most of those things are going to be customer needs. So as you were saying examples are for Amazon would be you know low price fast delivery. Nobody 10 years is going to say I love Amazon. I just wish they delivered a little more slowly. They focus too much on what&#39;s changing instead of what&#39;s not changing. 
그런 것들의 대부분이 바로 고개의 니즈가 될 것입니다. 아마존의 예로는 낮은 가격과 빠른 배송이 있습니다. 누구도 아마존이 좋은데 배송이 조금만 더 느렸으면 좋겠어라 하진 않을 것입니다. 사람들은 변하지 않는 것 대신 변하는 것에 너무 집중하곤 합니다.</p>
<p>// next
H :
So sometimes intuition and data align and that makes it easy to make decisions. But often times the data and the anecdotal evidence or the intuition are misaligned.</p>
<hr>
<p>Diametrically opposite</p>
<ul>
<li>정반대의</li>
<li>His view is diametrically opposite to mine</li>
</ul>
<p>Disruptive</p>
<ul>
<li>파괴하는, 혁신적인</li>
<li>Netflix was disruptive force in the movie industry</li>
</ul>
<p>build a strategy around ~</p>
<ul>
<li>~을 중심으로 전략을 짜다</li>
</ul>
<p>being in your presence</p>
<ul>
<li>당신과 함께 있는 것</li>
</ul>
<p>One of the things I&#39;ve learned from you is</p>
<ul>
<li>당신에게 배운 것 중 하나는 ~ 이다.</li>
</ul>
<p>10 years from now</p>
<ul>
<li>지금으로부터 10년 뒤..</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[제프 베조스 '변하지 않는 것' - 영어 회화 공부 (1)]]></title>
            <link>https://velog.io/@ziggy_stardust/%EC%A0%9C%ED%94%84-%EB%B2%A0%EC%A1%B0%EC%8A%A4-%EB%B3%80%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EA%B2%83-%EC%98%81%EC%96%B4-%ED%9A%8C%ED%99%94-%EA%B3%B5%EB%B6%80-1</link>
            <guid>https://velog.io/@ziggy_stardust/%EC%A0%9C%ED%94%84-%EB%B2%A0%EC%A1%B0%EC%8A%A4-%EB%B3%80%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EA%B2%83-%EC%98%81%EC%96%B4-%ED%9A%8C%ED%99%94-%EA%B3%B5%EB%B6%80-1</guid>
            <pubDate>Sat, 15 Nov 2025 10:18:59 GMT</pubDate>
            <description><![CDATA[<p>평소에 즐겨보던 유튜브 채널 &#39;비즈카페&#39; 에서 마음에 드는 영상이 올라와 따라 듣고, 적고 말해보며 회화 공부를 합니다.</p>
<p><a href="https://youtu.be/QvtOB8CLomc?si=rqRLD0kkhyxScPPW">관련 링크</a></p>
<p>H : 
You started Amazon &lt;out of your garage&gt; and you saw Internet sort of happening before it happend.
차고에서 아마존을 시작하셨죠 그리고 인터넷이 실제 흥하기 전에 그 가능성을 보았습니다.</p>
<p>you know, how did you what confidence did you have in taking a leap that you could create a company like the one that you you created?
아시듯, 어떤 자신감이 당신이 만든 회사처럼 도약할 수 있게 했나요? 
(처음 시작할 때도 이렇게 성공할 것이란 확신이 있었나요? 라고 본 영상에선 자연스럽게 해석한다.)</p>
<p>JB : 
Well, you know, all big things start small. 
음.. 모든 엄청난 성과는 작은 것에서부터 시작되죠.</p>
<p>So you&#39;ve got to you got to accept that. that&#39;s the way it is. 
그러니 그것이 초라하고 쉽지 않음을 인정하는 것부터 시작해야합니다. </p>
<p>And you got to plant acorn and work hard and water it and nurture it and maybe it grows into a big tree. 
도토리를 심고 물을 주며 열심히 잘 가꿔야 큰 나무로 자랄 수 있습니다.</p>
<p>the for me I made that decision and I knew that if I didn&#39;t try I would always be haunted by that. 
저한테 있어 제가 아마존을 세운단 결정을 하지 않았다면 평생 그것을 후회했을거란걸 알았어요.</p>
<p>I would always have reget. I would always wonder what might have been.
전 언제나 후회하고 언제나 도전을 했다면 어땠을까 스스로 자책했을 겁니다.</p>
<p>And I think that that&#39;s the best way when you&#39;re making a deeply personal decision about your own life. 
인생에서 중요한 결정을 내려야할 때 꼭 고려해야할 것은</p>
<p>You can make pros and cons list. You can be as analytical as you want. 
이점과 실점을 리스트로 만들고 당신이 원하는대로 분석할 수 있습니다.</p>
<p>But at the end of the day, you have to project youself forward you know, age80, you know, we&#39;re all living longer now, maybe age90, and say, &quot;Do I want to be haunted by that regret? I want to minimize the number of regrets I have in my life.&quot; 
하지만 여러분이 죽기 직전에 자신의 미래를 생각해 봐야합니다.
그리고 80세, 음 우리는 현재보다 더 오래살 것이고 90세라 했을 때 후회하며 스스로 자책하고 싶은지 또는 이런 후회를 최소화하고 싶은지 생각해야합니다.</p>
<p>And when you start thinking that way, that you&#39;re looking back on your life from age 90, most regrets are acts of omission. They&#39;re things we didn&#39;t do.. 
90세가 되어 인생을 되돌아본다면 깨달을 겁니다. 대부분 후회란 &#39;하지 않은 것&#39;에서부터 온다는걸 말이죠.</p>
<hr>
<p>be haunted by</p>
<ul>
<li>쫓기다. 시달리다.</li>
<li>이번 여상에선 후회로부터 쫓기고 자책하는 의미로 사용</li>
</ul>
<p>what might have been</p>
<ul>
<li>과거에일어났을지 모르는 일</li>
</ul>
<p>most regrets are acts of omission</p>
<ul>
<li>대부분의 후회는 하지 않는다는 행위이다.. (한글론 매끄럽지않네)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SAP 스노우플레이크 ]]></title>
            <link>https://velog.io/@ziggy_stardust/SAP-%EC%8A%A4%EB%85%B8%EC%9A%B0%ED%94%8C%EB%A0%88%EC%9D%B4%ED%81%AC</link>
            <guid>https://velog.io/@ziggy_stardust/SAP-%EC%8A%A4%EB%85%B8%EC%9A%B0%ED%94%8C%EB%A0%88%EC%9D%B4%ED%81%AC</guid>
            <pubDate>Fri, 14 Nov 2025 14:10:12 GMT</pubDate>
            <description><![CDATA[<p>기회가 되어 스노우플레이크, SAP 에 관하여 키워드를 들었습니다.</p>
<p>평소에도 아키텍처에 대해서 고민을 하다보면 늘 DB에서 병목이 발생해 어떻게 하면 쓰기 부하 같은 문제를 잘 해결할 수 있을까에 관해 고민했습니다. Primary, Secondary 를 나눠 이러한 문제를 해결하려 시도할 수 있고 appendOnly로 데이터를 다뤄 이런 문제를 해결하려는 방법도 보았습니다.</p>
<p>이번에 SAP 와 스노우플레이크가 협력을 하게 되었는데 데이터의 복제 없이 양방향으로 데이터를 공유하며 엔터프라이즈급 AI 구축을 돕는다고 한다. (출시는 26년도 상반기)</p>
<p>제로카피라면 사실 아파치 카프카 같은 기술에서도 사용되는 최적화 접근법이긴한데 이러한 행보가 흥미로워 조금 정리를 해보려합니다.</p>
<hr>
<h4 id="sap">SAP</h4>
<p>SAP란 이름부터 생소할 수 있습니다. ERP 시스템을 만드는 독일의 회사입니다.</p>
<p>ERP는 기업내 자원을 관리하는 프로그램으로 &#39;직원 급여 데이터&#39;, &#39;제품 재고 정보&#39;, &#39;고객 주문 내역&#39;, &#39;회계 장부&#39; 등이 SAP 안에 저장됩니다.</p>
<p>그래서 급여, 재고, 주문 데이터 같은건 SAP 데이터라고 볼 수 있습니다. 그외 엑셀 파일, 이메일, 웹사이트 로그 같은건 비SAP 데이터라 할 수 있습니다.</p>
<p>기업에서의 업무를 수행하려면 단순히 SAP 데이터 뿐만 아니라 비SAP 데이터 또한 필요로 합니다.</p>
<hr>
<h4 id="sap-snowflake">SAP Snowflake</h4>
<p>그래서 SAP과 Snowflake 가 협약을 맺어 만들 SAP Snowflake 가 하는 일은 다음과 같습니다.</p>
<ul>
<li>제로카피 공유로 SAP와 Snowflake 데이터 조율<ul>
<li>데이터 복제 없이 통합 관리</li>
</ul>
</li>
<li>지능형 애플리케이션 개발 가속화<ul>
<li>AI 기반 앱 빠르게 구축</li>
</ul>
</li>
<li>신뢰할 수 있는 데이터 기반 엔터프라이즈급 AI 지원<ul>
<li>검증된 데이터로 AI 활용</li>
</ul>
</li>
</ul>
<p>간단하게 정리하자면 SAP, 비 SAP 데이터들을 복제 없이 실시간으로 관리하여 한정된 범위 내에서 일관성을 지키고 보안적인 부분을 향상시킬 수 있습니다. 이렇게 데이터가 최소한의 범위에서 지켜지니 총소유비용(TCO, 데이터 복제, 저장 등 비용) 또한 절감이 가능합니다.</p>
<p>복제가 필요없이 실시간적으로 사용하니 ETL 같은 데이터 처리 없이 바로 사용이 가능할 것으로 기대할 수 있습니다.</p>
<hr>
<h4 id="제로카피">제로카피</h4>
<p>대단한 일을 하는 것으로 보이는데 그중에서도 가장 눈에 들어오는건 제로카피입니다.</p>
<p>옛날 학부 시절에 폰 노이만 구조를 공부하면 복사는 반드시 일어날 수 밖에 없겠구나라고 생각을 하며 자랐는데 이런 저에게 제로카피란 키워드는 너무나도 강렬하게 다가옵니다.</p>
<p>일단 2차 저장장치 -&gt; 메모리 -&gt; 캐시 이러한 흐름에서의 복사는 일어날 수 밖에 없습니다. 여기서 언급되는 카피는 SAP, 비SAP 데이터를 저장하는 장치들 사이에서 발생하는 복사이다.
SAP 데이터를 다루는 장치에서 비SAP 데이터를 이용하기 위해 ETL 같은 과정 등을 통해 저장을 하지 않고 네트워크 통신 등을 통해 필요한 데이터를 가져와서 처리하는 것입니다. (Data Federation)</p>
<p>캐시를 고려할 때 고려해야하는 장, 단점과도 일맥상통한 부분이 있네요. 그래도 신뢰할 수 있는 단일지점을 가진다는건 꽤 흥미롭습니다. </p>
<p>SAP Snowflake 만 아니라 Snowflake 내부에서도 제로카피 메커니즘이 있다하는데 따로 정리를 해봐야겠습니다.</p>
<hr>
<p><a href="https://medium.com/towards-data-engineering/what-i-learnt-this-week-about-sap-snowflake-and-bdc-connect-for-snowflake-ce1363f021b2">참고 아티클</a>
<a href="https://www.sap.com/korea/products/data-cloud/snowflake.html">참고 아티클</a>
<a href="https://www.hellot.net/news/article.html?no=106875">참고 기사</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[상태 코드 정리]]></title>
            <link>https://velog.io/@ziggy_stardust/%EC%83%81%ED%83%9C-%EC%BD%94%EB%93%9C-%EB%85%B8%EC%85%98%EB%8C%80%EC%9A%A9</link>
            <guid>https://velog.io/@ziggy_stardust/%EC%83%81%ED%83%9C-%EC%BD%94%EB%93%9C-%EB%85%B8%EC%85%98%EB%8C%80%EC%9A%A9</guid>
            <pubDate>Fri, 12 Sep 2025 04:39:24 GMT</pubDate>
            <description><![CDATA[<p>200 : OK : 요청 성공 (GET)
201 : Created : 생성 성공 (POST)
202 : Accepted : 요청 접수 O, 리소스 처리 X
204 : No Contents : 요청 성공 O, 내용 없음</p>
<hr>
<p>300 : 리다이렉트
300 : Multiple Choice : 요청 URI에 여러 리소스 존재
301 : Move Permanently : 요청 URI가 새 위치로 옮겨감
304 : Not Modified : 요청 URI의 내용이 변경 X</p>
<hr>
<p>400번대 : 클라 오류
400 : Bad Request : API에서 정의되지 않은 요청 들어옴
401 : Unauthorized : 인증 오류
403 : Forbidden : 권한 밖 접근 시도
404 : Not Found : 요청 URI에 대한 리소스 존재 X
405 : Method Not Allowed : API에서 정의되지 않은 메소드 호출
406 : Not Acceptable : 처리 불가
(구체적으로 무엇이 불가한지 몰라 예시 추가)</p>
<pre><code>  1. Accept 헤더 불일치

  GET /api/users HTTP/1.1
  Accept: application/xml

  → 서버는 JSON만 지원하는 경우
  HTTP/1.1 406 Not Acceptable

  2. Accept-Language 불일치

  GET /content HTTP/1.1
  Accept-Language: ko-KR

  → 서버는 영어만 지원하는 경우
  HTTP/1.1 406 Not Acceptable

  3. Accept-Encoding 불일치

  GET /file HTTP/1.1
  Accept-Encoding: br

  → 서버는 gzip만 지원하는 경우
  HTTP/1.1 406 Not Acceptable

  4. Accept-Charset 불일치

  GET /data HTTP/1.1
  Accept-Charset: iso-8859-1

  → 서버는 UTF-8만 지원하는 경우
  HTTP/1.1 406 Not Acceptable

  실제 예시

  GET /api/users HTTP/1.1
  Accept: text/plain

  → API가 JSON/XML만 제공
  HTTP/1.1 406 Not Acceptable
  Content-Type: application/json

  {
    &quot;error&quot;: &quot;Available formats: application/json, application/xml&quot;
  }

  요약: 클라이언트가 요청한 형식을 서버가 제공할 수 없을 때 406을 응답합니다.</code></pre><p>  기대 응답을 못할때
408 : Request Timeout : 요청 대기 시간 초과
409 : Conflict : 모순
429 : Too Many Request : 요청 횟수 상한 초과</p>
<hr>
<p>500 : 서버 오류
500 : Internal Server Error : 서버 내부 오류
502 : Bad Gateway : 게이트웨이 오류
503 : Service Unavailable : 서비스 이용 불가
504 : Gateway Timeout : 게이트웨이 시간 초과</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[동기, 비동기, 블로킹, 논블로킹]]></title>
            <link>https://velog.io/@ziggy_stardust/%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%B8%94%EB%A1%9C%ED%82%B9-%EB%85%BC%EB%B8%94%EB%A1%9C%ED%82%B9</link>
            <guid>https://velog.io/@ziggy_stardust/%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%B8%94%EB%A1%9C%ED%82%B9-%EB%85%BC%EB%B8%94%EB%A1%9C%ED%82%B9</guid>
            <pubDate>Thu, 11 Sep 2025 09:42:00 GMT</pubDate>
            <description><![CDATA[<p>개인적인 생각을 정리해봤습니다. 다른 분들과 의견이 다를 수 있고 틀린 부분이 있을 수 있으니 그런 부분은 알려주신다면 정말 감사하겠습니다.</p>
<hr>
<p>프레임워크나 프로그래밍 언어의 컨셉을 깊게 공부한다면 당연히 마주하는 개념 <strong>동기</strong>, <strong>비동기</strong>, <strong>블로킹</strong>, <strong>논블로킹</strong>.</p>
<h3 id="왜-이해하기-어려울까">왜 이해하기 어려울까</h3>
<p>잘 정리된 글을 읽어봐도 남에게 명쾌하게 설명하기는 힘들었다. 나자신부터 제대로 이해를 못하였기 때문인데 왜 이렇게 이해하기가 힘들었을까.</p>
<p>원인은 이러한 개념이 어떠한 하나의 행위에서 발생할 수 있는 <strong>&#39;현상&#39;</strong> 임을 분명히하지 않아 그런것이라 생각한다.</p>
<p>그 행위를 프로그래밍 언어 느낌나도록 설명을 하자면 함수A에서 함수B를 호출했을 때 어떠한 동작을 하는가이다. </p>
<p>여기서 어떠한 동작은 프레임워크, 프로그래밍 언어에 따라서 다르고 같은 프레임워크, 프로그래밍 언어에서도 다양하다.</p>
<p>내가 말하고 싶은건 어떠한 원인으로부터 나오게 될 결과, 즉 현상부터 집중할 것이 아니라 그 원인, 행위에서부터 찾아가자는 것이다.</p>
<hr>
<h3 id="행위로부터-현상을-분석해보자">행위로부터 현상을 분석해보자</h3>
<p><strong>[동기, 블로킹이 발생하는 행위]</strong>
함수A가 함수B를 호출한다. 이 과정에서 함수A는 단순하게 함수B가 무사히 완료하고 결과값을 반환할때까지 기다릴 수 있다. 평범한 사람의 사고로 생각했을 때 이 과정은 정말 자연스럽다고 생각한다. 이러한 과정에서 발생한 &#39;현상&#39;을 정리해보자. 우선 처음 제어권은 함수A가 가지고 있다. 그렇기에 제어권을 가지고 함수B를 호출할 수 있다. 그 뒤에는 함수B를 호출하며 함수B에게 제어권을 넘기고 함수B의 로직을 수행하도록 한다. 제어권을 넘겼고 함수A는 함수B의 결과를 얻기 위해 대기를 한다. 이 과정에서 함수B의 결과를 얻기위해 함수A가 함수B에게 제어권을 넘기며 호출 이후의 로직을 수행하지 않은건 <strong>블로킹</strong> 현상으로 볼 수 있다. 그리고 함수A는 함수B가 결과를 줄 때까지 함수B의 결과완료상태에 신경써야한다.(함수B가 완료되어야 응답을 받고 함수A의 다음 로직을 수행할 수 있기에) 이건 <strong>동기</strong> 현상으로 볼 수 있다. 동기와 블로킹 현상은 <strong>단순하게</strong> 작업을 처리하려할 때 발생할 수 있는 현상이다.</p>
<p><strong>[비동기, 논블로킹이 발생하는 행위]</strong>
앞서 강조한듯 이러한 현상은 <strong>단순하게</strong> 작업을 처리하면서 발생하는 일이다. 소프트웨어는 생명과도 유사하게 성장한다. 단순하게는 서비스의 품질을 만족하지 못할 수 있다. 그래서 조금 더 <strong>효율적으로</strong> 작업을 처리해야한다.</p>
<p>함수A가 함수B를 호출한다. 함수A는 함수B를 호출했지만 함수B의 결과를 기다리지 않고 제어권을 함수B에 줬다가 바로 돌려받는다. 그리고 함수A 자신의 작업을 이어간다. 이제 기다리지 않고 자신의 작업을 바로 이어갈 수 있으니 <strong>더 효율적으로</strong> 작업을 수행할 수 있게 된 것이다. 그리고 함수B가 완료되었을 때 후속작업을 처리해준다. 이 행위에서 함수A가 함수B 호출 이후 제어권을 바로 돌려받고 함수A 자신의 작업을 바로 수행하는 것은 <strong>논블로킹</strong> 현상으로 볼 수 있다. 그리고 함수A가 함수B 호출 후 함수B의 완료여부에 무신경한 것은 <strong>비동기</strong> 현상으로 볼 수 있다.</p>
<p><strong>[그 외]</strong>
대부분의 프레임워크나 프로그래밍 언어에서는 위 두 가지 패턴이 많이 사용된다. 정말 단순하게 해결하던가 아니면 최대한 효율적으로 하던가 둘을 선택한다. 하지만 (동기, 논블로킹) , (비동기, 블로킹) 이러한 현상이 나타나는 행위들 또한 존재한다.
이런 두 방식에는 현실적인 괴리와 비효율성이 존재해서 보기가 힘들다.</p>
<p><strong>[동기, 논블로킹이 발생하는 행위]</strong>
동기, 논블로킹 현상이 일어나는 대표적인 예시로는 &#39;폴링&#39; 방식이 있다. 주기적으로 요청을 보내는 방식이다. 구체적인 예로는 실시간과 유사하게 기능을 구현하려고 짧은 주기로 서버에 요청을 보내는 그런 방식을 의미한다. 폴링은 자신의 원래 목적 로직은 수행하면서 주기적으로 자신이 원하는 서버의 상태를 주기적으로 신경을 쓴다. 폴링 방식은 이러한 주기적인 요청으로 인해 비효율적이다는 문제를 겪는다. 이러한 행위에서 주기적으로 서버의 상태를 신경 쓰는게 <strong>동기</strong> 현상으로 볼 수 있다. 위에서 동기, 블로킹 방식의 예에선 함수B의 완료여부를 주기적으로 신경 썼던 것과 같은 맥락이다.</p>
<p><strong>[비동기, 블로킹이 발생하는 행위]</strong>
비동기, 블로킹 현상이 일어나는 행위는 정말 비효율적인 부분이 크다. 그렇기에 적절한 예가 떠오르지 않는다. 그저 현실묘사를 통해서 어떤 부분이 비효율적인가를 생각해보자. </p>
<p>카페에 가서 &#39;아이스 아메리카노&#39;를 주문시킨다고 하자.
프론트에서 주문을 한다. 이는 함수B를 호출했다고 봐도 다르지 않다. 이제 &#39;아이스 아메리카노&#39;가 완성되면 알아서 호출해줄 것이기에 신경쓰지 않고 돌아와 알고리즘 문제를 하나 더 풀어도 괜찮다. 하지만 어찌된 영문인지 프론트 앞에 어두커니 서서 그 자리를 버티는 것이다. 지나가던 사람이 주문자를 쳐다보고 이 사람이하고 있는 생산적인 행위가 무엇이냐라는 질문에 대답을 한다면 여지없이 &#39;숨쉬기&#39; 혹은 &#39;원활한 혈액순환&#39; 정도로 말을 꺼낼 것이다.
기껏 음료를 주문하고 자유로운 시간이 생겼음에도 아무런 행동 없이 기다리는 것은 비효율적, 낭비라고 말할 수 있다.</p>
<p>일부러 이러한 동작을하는 경우는 크게 없기에 적절한 예시를 찾지 못하였다. 어쩌면 컴퓨터라는 기기의 상태 회복과 부담을 줄이기 위해 이러한 동작을 유도할 수도 있겠지만 더 나은 방식이 있을 것 같다.</p>
<p>지금까지는 단순히 행위로부터 현상을 분석한다는 느낌으로 동기, 비동기, 블로킹, 논블로킹을 정리해보았다. 즉 개념만을 설명하였고 조금 더 기술적인 내용도 섞어내는 것이 와닿을 것이다.</p>
<hr>
<h3 id="행위는-어떻게-구현되는가">행위는 어떻게 구현되는가</h3>
<p>이러한 행위를 프레임워크, 프로그래밍 언어에서 구현해내기 위해 어떠한 기술들을 선택했을까.</p>
<p>이번에는 일반적인 (동기, 블로킹), (비동기, 논블로킹) 에 대해서만 알아보자.</p>
<p>자바, 자바스크립트 같은 프로그래밍 언어에서는 논블로킹은 다른 스레드에게 작업을 위임함으로 제어권을 되찾는다. 함수B가 제어권을 바로 빼앗긴다해도 함수B 또한 수행이 되어야 한다. 즉 함수B 독자적인 제어권을 가진다는 것이다. 이를 스레드를 통해서 이뤄낸다. 자바의 멀티스레드, 자바스크립트 노드에서의 워커스레드가 예시이다. </p>
<hr>
<h3 id="비동기-논블로킹은-무엇을-최적화하나">비동기, 논블로킹은 무엇을 최적화하나</h3>
<p>비동기, 논블로킹을 유도함으로 만들어내는 최적화는 선후 작업이 독립된 경우에 가능하다.</p>
<p>모든 상황을 효율적으로 처리할 순 없다. 세상에는 효율적인 처리를 위해 나눌 수 있는 일이 있고 나눌 수 없는 일이 있다. 원자를 나누는게 상당히 어려운 일인듯 효율적으로 처리하고 싶어 작업을 나누고 싶어도 불가능한 일이 존재한다는 것이다. 그건 선후행 작업간에 강한 결합, 의존이 존재할 때이다. 이 경우엔 비동기, 논블로킹이 이뤄지도록 나눠 실행할 수가 없다.</p>
<p>한 가지 예시로는 사진을 다운받아 크기를 조정하는 작업이 있다하자.</p>
<pre><code>// 1. 사진 다운로드 후 저장
// 2. 사진 크기 조정</code></pre><p>비동기, 논블로킹이 효율적이기에 어느 상황에서나 만능기처럼 동작할거 같지만 2번 작업은 1번이 완료하기 전엔 논리적으로 실행이 불가능하다. 블로킹이 반드시 일어날 수 밖에 없는 것이다.
그래서 비동기, 논블로킹이 효율화, 최적화를 시키는 것은 선후행 작업이 독립적인 경우일 때다.</p>
<p>얕은 예시로 아래와 같다.</p>
<pre><code>// 1. API 접근 정보 로깅
// 2. 결과값 계산</code></pre><p>1번 로깅이 끝나는 것과 2번 작업이 시작하는 것 사이에는 큰 연관이 없다. (요구사항에 따라 다르지만 1번 작업의 실패가 전체 작업의 실패로 처리해야할 정도로 중요한 것이 아니라면) 이런 경우 1번 작업은 비동기, 논블로킹 현상이 일어나게끔 다른 스레드에 위임해서 실패하던 성공하던 신경쓰지 말고 2번 작업을 바로 시작하는 것이다.</p>
<hr>
<h4 id="주절주절">주절주절</h4>
<p>행위나 현상으로 설명을 하는데 용어가 적절한지 모르겠다. 더 적절한 것이 있다면 좋겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 로 진행하는 간단한 영화 예매 시스템]]></title>
            <link>https://velog.io/@ziggy_stardust/t</link>
            <guid>https://velog.io/@ziggy_stardust/t</guid>
            <pubDate>Sun, 07 Sep 2025 14:52:52 GMT</pubDate>
            <description><![CDATA[<h1 id="간단한-영화-예매-시스템">간단한 영화 예매 시스템</h1>
<h2 id="요구사항">요구사항</h2>
<ul>
<li>영화: 1개 고정</li>
<li>사용자: 정수 ID로 구분</li>
<li>좌석: 10x10 = 100석</li>
<li>예매/취소 기능</li>
</ul>
<h2 id="구현할-기능">구현할 기능</h2>
<h3 id="1-기본-모델">1. 기본 모델</h3>
<pre><code class="language-typescript">interface Seat {
  row: number;        // 1-10
  col: number;        // 1-10
  isBooked: boolean;
  bookedBy?: number;  // 사용자 ID
}

enum PaymentMethod {
  CREDIT_CARD = &#39;CREDIT_CARD&#39;,
  BANK_TRANSFER = &#39;BANK_TRANSFER&#39;,
  KAKAO_PAY = &#39;KAKAO_PAY&#39;,
  NAVER_PAY = &#39;NAVER_PAY&#39;,
  TOSS_PAY = &#39;TOSS_PAY&#39;
}

enum PaymentStatus {
  PENDING = &#39;PENDING&#39;,
  COMPLETED = &#39;COMPLETED&#39;,
  FAILED = &#39;FAILED&#39;,
  REFUNDED = &#39;REFUNDED&#39;
}

interface Payment {
  id: string;
  amount: number;
  method: PaymentMethod;
  status: PaymentStatus;
  transactionId?: string;
  processedAt?: Date;
}

interface Booking {
  id: string;
  userId: number;
  seatRow: number;
  seatCol: number;
  bookingTime: Date;
  payment: Payment;
  status: &#39;RESERVED&#39; | &#39;CONFIRMED&#39; | &#39;CANCELLED&#39;;
}

class Movie {
  constructor(
    public title: string = &quot;어벤져스: 엔드게임&quot;,
    public duration: number = 180,
    public price: number = 12000
  ) {}
}</code></pre>
<h3 id="2-결제-시스템-strategy-pattern">2. 결제 시스템 (Strategy Pattern)</h3>
<pre><code class="language-typescript">interface PaymentProcessor {
  processPayment(amount: number, paymentData: any): Promise&lt;Payment&gt;;
  refundPayment(transactionId: string): Promise&lt;boolean&gt;;
}

class CreditCardProcessor implements PaymentProcessor {
  async processPayment(amount: number, cardData: any): Promise&lt;Payment&gt; {
    // 신용카드 결제 로직
    return {
      id: crypto.randomUUID(),
      amount,
      method: PaymentMethod.CREDIT_CARD,
      status: PaymentStatus.COMPLETED,
      transactionId: `CC_${Date.now()}`,
      processedAt: new Date()
    };
  }

  async refundPayment(transactionId: string): Promise&lt;boolean&gt; {
    // 환불 로직
    return true;
  }
}

class KakaoPayProcessor implements PaymentProcessor {
  async processPayment(amount: number, kakaoData: any): Promise&lt;Payment&gt; {
    // 카카오페이 결제 로직
    return {
      id: crypto.randomUUID(),
      amount,
      method: PaymentMethod.KAKAO_PAY,
      status: PaymentStatus.COMPLETED,
      transactionId: `KAKAO_${Date.now()}`,
      processedAt: new Date()
    };
  }

  async refundPayment(transactionId: string): Promise&lt;boolean&gt; {
    return true;
  }
}

class PaymentProcessorFactory {
  static createProcessor(method: PaymentMethod): PaymentProcessor {
    switch (method) {
      case PaymentMethod.CREDIT_CARD:
        return new CreditCardProcessor();
      case PaymentMethod.KAKAO_PAY:
        return new KakaoPayProcessor();
      case PaymentMethod.NAVER_PAY:
        return new NaverPayProcessor();
      case PaymentMethod.TOSS_PAY:
        return new TossPayProcessor();
      default:
        throw new Error(`Unsupported payment method: ${method}`);
    }
  }
}</code></pre>
<h3 id="3-예매-시스템-클래스">3. 예매 시스템 클래스</h3>
<pre><code class="language-typescript">class BookingSystem {
  private seats: Seat[][];
  private bookings: Map&lt;string, Booking&gt;;
  private movie: Movie;
  private paymentProcessors: Map&lt;PaymentMethod, PaymentProcessor&gt;;

  constructor() {
    this.initializeSeats();
    this.bookings = new Map();
    this.movie = new Movie();
    this.initializePaymentProcessors();
  }

  // 좌석 초기화
  private initializeSeats(): void {}

  // 결제 프로세서 초기화
  private initializePaymentProcessors(): void {
    this.paymentProcessors = new Map();
    Object.values(PaymentMethod).forEach(method =&gt; {
      this.paymentProcessors.set(method, PaymentProcessorFactory.createProcessor(method));
    });
  }

  // 예매하기 (결제 포함)
  public async bookSeat(
    userId: number, 
    row: number, 
    col: number, 
    paymentMethod: PaymentMethod, 
    paymentData: any
  ): Promise&lt;string | null&gt; {
    // 좌석 확인
    if (this.isSeatBooked(row, col)) {
      return null;
    }

    // 결제 처리
    const processor = this.paymentProcessors.get(paymentMethod);
    if (!processor) {
      throw new Error(&#39;Payment processor not found&#39;);
    }

    try {
      const payment = await processor.processPayment(this.movie.price, paymentData);

      // 예매 생성
      const booking: Booking = {
        id: crypto.randomUUID(),
        userId,
        seatRow: row,
        seatCol: col,
        bookingTime: new Date(),
        payment,
        status: &#39;CONFIRMED&#39;
      };

      this.bookings.set(booking.id, booking);
      this.seats[row-1][col-1].isBooked = true;
      this.seats[row-1][col-1].bookedBy = userId;

      return booking.id;
    } catch (error) {
      return null;
    }
  }

  // 예매 취소 (환불 포함)
  public async cancelBooking(bookingId: string): Promise&lt;boolean&gt; {
    const booking = this.bookings.get(bookingId);
    if (!booking || booking.status === &#39;CANCELLED&#39;) {
      return false;
    }

    // 환불 처리
    const processor = this.paymentProcessors.get(booking.payment.method);
    if (processor &amp;&amp; booking.payment.transactionId) {
      await processor.refundPayment(booking.payment.transactionId);
      booking.payment.status = PaymentStatus.REFUNDED;
    }

    // 좌석 해제
    this.seats[booking.seatRow-1][booking.seatCol-1].isBooked = false;
    this.seats[booking.seatRow-1][booking.seatCol-1].bookedBy = undefined;

    booking.status = &#39;CANCELLED&#39;;
    return true;
  }

  // 좌석 현황 보기
  public getSeatStatus(): Seat[][] {}

  // 특정 사용자 예매 내역
  public getUserBookings(userId: number): Booking[] {}

  // 결제 내역 조회
  public getPaymentHistory(userId: number): Payment[] {
    return Array.from(this.bookings.values())
      .filter(booking =&gt; booking.userId === userId)
      .map(booking =&gt; booking.payment);
  }
}</code></pre>
<h2 id="적용할-디자인-패턴">적용할 디자인 패턴</h2>
<h3 id="1-strategy-pattern-결제-시스템">1. Strategy Pattern (결제 시스템)</h3>
<pre><code class="language-typescript">// 이미 위에서 구현됨
// PaymentProcessor 인터페이스와 각 결제수단별 구현체</code></pre>
<h3 id="2-factory-pattern-결제-프로세서-생성">2. Factory Pattern (결제 프로세서 생성)</h3>
<pre><code class="language-typescript">// PaymentProcessorFactory 클래스로 구현됨</code></pre>
<h3 id="3-singleton-pattern">3. Singleton Pattern</h3>
<pre><code class="language-typescript">class BookingSystem {
  private static instance: BookingSystem;

  public static getInstance(): BookingSystem {
    if (!BookingSystem.instance) {
      BookingSystem.instance = new BookingSystem();
    }
    return BookingSystem.instance;
  }
}</code></pre>
<h3 id="4-command-pattern-예매결제-트랜잭션">4. Command Pattern (예매/결제 트랜잭션)</h3>
<pre><code class="language-typescript">interface BookingCommand {
  execute(): Promise&lt;string | null&gt;;
  undo(): Promise&lt;boolean&gt;;
}

class BookSeatCommand implements BookingCommand {
  constructor(
    private system: BookingSystem,
    private userId: number,
    private row: number,
    private col: number,
    private paymentMethod: PaymentMethod,
    private paymentData: any
  ) {}

  async execute(): Promise&lt;string | null&gt; {
    return await this.system.bookSeat(
      this.userId, 
      this.row, 
      this.col, 
      this.paymentMethod, 
      this.paymentData
    );
  }

  async undo(): Promise&lt;boolean&gt; {
    // 예매 취소 로직
    return true;
  }
}</code></pre>
<h2 id="활용할-내장-함수">활용할 내장 함수</h2>
<h3 id="array-메소드">Array 메소드</h3>
<pre><code class="language-typescript">// 빈 좌석 찾기
seats.flat().filter(seat =&gt; !seat.isBooked)

// 사용자별 예매 찾기
bookings.filter(booking =&gt; booking.userId === userId)

// 좌석 존재 여부
seats.some(row =&gt; row.some(seat =&gt; seat.row === targetRow))

// 모든 좌석 예매됨 확인
seats.every(row =&gt; row.every(seat =&gt; seat.isBooked))</code></pre>
<h3 id="mapset-활용">Map/Set 활용</h3>
<pre><code class="language-typescript">// 예매 저장소
private bookings = new Map&lt;string, Booking&gt;();

// 중복 예매 방지
private activeBookings = new Set&lt;string&gt;();</code></pre>
<h2 id="테스트-시나리오">테스트 시나리오</h2>
<h3 id="1-기본-기능-테스트">1. 기본 기능 테스트</h3>
<pre><code class="language-typescript">describe(&#39;BookingSystem&#39;, () =&gt; {
  let system: BookingSystem;

  beforeEach(() =&gt; {
    system = BookingSystem.getInstance();
  });

  test(&#39;should book available seat with payment&#39;, async () =&gt; {
    const bookingId = await system.bookSeat(
      1, 5, 5, 
      PaymentMethod.CREDIT_CARD, 
      { cardNumber: &#39;1234-5678-9012-3456&#39; }
    );
    expect(bookingId).toBeTruthy();
  });

  test(&#39;should not book occupied seat&#39;, async () =&gt; {
    await system.bookSeat(1, 5, 5, PaymentMethod.CREDIT_CARD, {});
    const result = await system.bookSeat(2, 5, 5, PaymentMethod.KAKAO_PAY, {});
    expect(result).toBeNull();
  });

  test(&#39;should process different payment methods&#39;, async () =&gt; {
    const cardBooking = await system.bookSeat(1, 1, 1, PaymentMethod.CREDIT_CARD, {});
    const kakaoBooking = await system.bookSeat(2, 1, 2, PaymentMethod.KAKAO_PAY, {});
    const naverBooking = await system.bookSeat(3, 1, 3, PaymentMethod.NAVER_PAY, {});

    expect(cardBooking).toBeTruthy();
    expect(kakaoBooking).toBeTruthy();
    expect(naverBooking).toBeTruthy();
  });

  test(&#39;should cancel booking and refund payment&#39;, async () =&gt; {
    const bookingId = await system.bookSeat(1, 5, 5, PaymentMethod.TOSS_PAY, {});
    const cancelled = await system.cancelBooking(bookingId!);
    expect(cancelled).toBe(true);
  });
});

describe(&#39;PaymentProcessors&#39;, () =&gt; {
  test(&#39;should process credit card payment&#39;, async () =&gt; {
    const processor = new CreditCardProcessor();
    const payment = await processor.processPayment(12000, {});

    expect(payment.method).toBe(PaymentMethod.CREDIT_CARD);
    expect(payment.amount).toBe(12000);
    expect(payment.status).toBe(PaymentStatus.COMPLETED);
  });

  test(&#39;should create correct processor from factory&#39;, () =&gt; {
    const processor = PaymentProcessorFactory.createProcessor(PaymentMethod.KAKAO_PAY);
    expect(processor).toBeInstanceOf(KakaoPayProcessor);
  });
});</code></pre>
<h3 id="2-edge-case-테스트">2. Edge Case 테스트</h3>
<ul>
<li>잘못된 좌석 번호</li>
<li>동시 예매 시도</li>
<li>결제 실패 시 예매 롤백</li>
<li>존재하지 않는 예매 취소</li>
<li>지원하지 않는 결제수단</li>
</ul>
<h2 id="실행-계획">실행 계획</h2>
<h3 id="phase-1-기본-구조">Phase 1: 기본 구조</h3>
<ul>
<li><input disabled="" type="checkbox"> 인터페이스 및 클래스 정의 (Payment, Booking)</li>
<li><input disabled="" type="checkbox"> 좌석 초기화 로직</li>
<li><input disabled="" type="checkbox"> 결제 수단 enum 정의</li>
</ul>
<h3 id="phase-2-결제-시스템-구현">Phase 2: 결제 시스템 구현</h3>
<ul>
<li><input disabled="" type="checkbox"> PaymentProcessor 인터페이스 구현</li>
<li><input disabled="" type="checkbox"> 각 결제수단별 프로세서 클래스 (신용카드, 카카오페이, 네이버페이, 토스페이)</li>
<li><input disabled="" type="checkbox"> PaymentProcessorFactory 구현</li>
</ul>
<h3 id="phase-3-예매-시스템-통합">Phase 3: 예매 시스템 통합</h3>
<ul>
<li><input disabled="" type="checkbox"> 예매 로직 구현 (결제 포함)</li>
<li><input disabled="" type="checkbox"> 취소 로직 구현 (환불 포함)</li>
<li><input disabled="" type="checkbox"> 상태 조회 및 결제 내역 기능</li>
</ul>
<h3 id="phase-4-디자인-패턴-적용">Phase 4: 디자인 패턴 적용</h3>
<ul>
<li><input disabled="" type="checkbox"> Strategy Pattern (결제 시스템)</li>
<li><input disabled="" type="checkbox"> Factory Pattern (결제 프로세서 생성)</li>
<li><input disabled="" type="checkbox"> Singleton Pattern (예매 시스템)</li>
<li><input disabled="" type="checkbox"> Command Pattern (트랜잭션 관리)</li>
</ul>
<h3 id="phase-5-테스트">Phase 5: 테스트</h3>
<ul>
<li><input disabled="" type="checkbox"> Jest 설정</li>
<li><input disabled="" type="checkbox"> 결제 프로세서 단위 테스트</li>
<li><input disabled="" type="checkbox"> 예매 시스템 통합 테스트</li>
<li><input disabled="" type="checkbox"> 비동기 결제 처리 테스트</li>
</ul>
<h2 id="프로젝트-구조">프로젝트 구조</h2>
<pre><code>src/
├── models/
│   ├── Seat.ts
│   ├── Booking.ts
│   ├── Payment.ts
│   └── Movie.ts
├── services/
│   ├── BookingSystem.ts
│   └── PaymentService.ts
├── payments/
│   ├── PaymentProcessor.ts
│   ├── CreditCardProcessor.ts
│   ├── KakaoPayProcessor.ts
│   ├── NaverPayProcessor.ts
│   ├── TossPayProcessor.ts
│   └── PaymentProcessorFactory.ts
├── patterns/
│   ├── Singleton.ts
│   ├── Command.ts
│   ├── Strategy.ts
│   └── Factory.ts
├── utils/
│   └── validators.ts
└── tests/
    ├── BookingSystem.test.ts
    ├── PaymentProcessor.test.ts
    └── integration.test.ts</code></pre><p><strong>핵심 학습 포인트:</strong></p>
<ul>
<li>5가지 결제수단 (Strategy Pattern)</li>
<li>비동기 결제 처리 (Promise/async-await)</li>
<li>트랜잭션 관리 (Command Pattern)</li>
<li>에러 처리 및 롤백</li>
<li>포괄적인 테스트 커버리지</li>
</ul>
<p>실무에서 자주 사용되는 결제 시스템 패턴을 TypeScript로 완벽하게 학습할 수 있습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[카카오테크부트캠프 클라우드 인 제주 후기]]></title>
            <link>https://velog.io/@ziggy_stardust/%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%85%8C%ED%81%AC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%9D%B8-%EC%A0%9C%EC%A3%BC-%EC%A4%91%EA%B0%84-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@ziggy_stardust/%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%85%8C%ED%81%AC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%9D%B8-%EC%A0%9C%EC%A3%BC-%EC%A4%91%EA%B0%84-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Mon, 14 Apr 2025 14:48:35 GMT</pubDate>
            <description><![CDATA[<p><strong>해당 부트캠프 참여를 고민하는 사람들을 위해 글을 적습니다.</strong></p>
<p><strong>사람마다 상황과 가지고 있는 지식이 다르기에 무조건 추천한다는 말은 사용하지 않습니다.</strong></p>
<p><strong>이 글에선 그저 어떤 교육과정이 있었고 각 과정에서 어떤 점을 배우고 느꼈는지에 대해 서술합니다.</strong>
*<em>그리고 각 사람들의 상황에 따라 어떤 부분을 배울 수 있을지에 대해 추측해 보았습니다.
*</em></p>
<hr>
<h2 id="교육과정">교육과정</h2>
<p>해당 과정은 클라우드 엔지니어를 지향하며 다른 웹개발자 과정보다는 클라우드 기술에 조금 더 치중된 커리큐럼을 가집니다. (전반적인 웹기술도 다루기에 웹개발자를 지향하는 사람들에게도 적합합니다.)</p>
<h3 id="온라인-과정">온라인 과정</h3>
<p>교육은 6개월 동안 진행되며 앞의 4개월은 온라인에서 수업을 진행합니다.</p>
<p>온라인 기간 동안엔 전반적인 컴퓨터 과학과 개인 프로젝트 동안 사용하게될 프로그래밍 언어와 Git, Github 같은 도구 사용법을 배웁니다. 그리고 SNS 성향의 커뮤니티 서비스를 개발하게 됩니다. 뿐만 아니라 팀을 꾸려 다같이 면접 스터디나 특정 주제를 가지고 조사해 발표하는 시간을 가집니다. </p>
<p>컴퓨터 과학 등 강의 관련된 부분을 먼저 말씀드리자면 초반 몇 주 간 보이드라는 강사분께서 전반적인 컴퓨터 과학 지식과 자바스크립트 등 기술에 대해서 알려주셨습니다. 신경써서 만들어주신 교재가 었었음에도 이 과정이 꽤 타이트해 여기서 조금 버거워하신 분이 존재했습니다. 웹에 대한 전반적인 지식을 빠르게 배운 뒤 클라우드 쪽도 배워야해 어쩔 수 없는 부분이었던 것 같습니다. 전 그래도 어느 정도 알고있는 부분이 있어 그나마 따라갔던 것 같습니다. 이 부분은 시간을 투자하는 것 밖에 방법이 없어 보입니다. 이렇게 컴퓨터 과학, 웹 기술에 대한 부분을 배우고 나면 제프라는 강사분께서 본격적인 클라우드 기술에 대해서 알려주십니다. 역시 좋은 교재를 준비해주셨고 강의 전달력도 좋아 낯선 클라우드 기술도 잘 배울 수 있었습니다. 본인의 경험을 살려 잘 설명해주시는데 유익한 부분이 많았습니다. 수업 외 시간에도 특강 세션을 열어 지식전달을 해주시는데 도움을 많이 받았습니다.</p>
<p>그 다음은 개인 프로젝트에 대해 말씀드리겠습니다.
SNS 성향의 커뮤니티 서비스를 개인 프로젝트로 진행하며 풀스택 경험을 하게 됩니다. 이번 기수에선 자바스크립트를 가지고 개발하였습니다. (기수마다 백엔드 언어가 다를 수 있습니다. 결국 자바도 알려주십니다.)</p>
<p>프론트엔드는 바닐라 자바스크립트로 개발하며 백엔드는 express 프레임워크를 통해 만들었습니다. 커리큐럼을 잘 따라간 수강생들은 프론트엔드를 리액트로도 구현합니다. (관련된 교재와 수업을 제공해주셨습니다. 이번 기수에선 레오라는 강사분이 진행해주셨는데 적당한 수준으로 잘 진행해주셔서 저같이 백엔드를 지향하는 사람들도 쉽게 접근할 수 있었습니다.) </p>
<p>혼자서 처음부터 끝까지 진행하는 프로젝트기에 개발이 익숙하지 않은 분들은 꽤 고생을 하셨습니다. 강사님들이 꾸준히 질문을 받아주셨지만 예상 기간을 맞추지 못하는 분들도 존재했습니다. (접니다.) 그래도 개인 프로젝트이다보니 마감을 재촉하진 않고 각자의 상황과 속도에 맞게 개별적으로 진행할 수 있었습니다. 이러면 전체적으로 수업 진도가 늦어지는거 아닌가 걱정할 수 있는데 진도를 잘 따라가는 분들을 위해 추가 퀘스트 같은 것도 계속 내주셨습니다. 추가 퀘스트가 어떤 건지 궁금하실 수 있는데 와이어샤크를 이용해 웹통신간 오가는 패킷을 분석하게 하거나 정적 파일을 관리하는 오브젝트 스토리지 서버를 직접 구축하게끔 하는 내용들이였습니다. 외에도 프론트, 백, DevOps 분야마다 다양한 추가 퀘스트가 있습니다.</p>
<p>이렇게 개인 프로젝트를 개발하면 결과물로 처음부터 끝까지 혼자서 만든 커뮤니티 서비스가 만들어져 클라우드 환경에서 배포되어 운영할 수 있게 됩니다.
이렇게 배포되면 수강생들끼리 QA 단계를 가지고 서로가 만든 웹서비스에서 취약점과 사용자관점에서의 개선안을 받게 됩니다. 이 과정이 꽤 재밌고 유익합니다. (XSS 나 의도한 CSS 스타일을 벗어나게하는 특수문자라던가 혼자서 개발할 땐 잘 경험하지 못했던 부분을 경험할 수 있었습니다.)</p>
<blockquote>
<p>전 자바 백엔드 개발자를 지향해서 자바스크립트로 진행하는 것이 조금 아쉬웠는데 패러다임이 조금 다른 자바스크립트를 통해 백엔드를 구축하니 각 언어의 패러다임이 어떠한 차이를 가지는지 조금 더 직접적으로 느낄 수 있었습니다. 그리고 Express 를 사용하면서 직접 적절한 디자인 패턴으로 기능을 구현했는데 이 과정에서 잘 만들어진 다른 프레임워크의 내부 구현까지 예상할 수 있어 다른 프레임워크를 공부할 때도 큰 도움이 되었습니다. 그리고 자바스크립트에서 사용한 함수형 패턴, 비동기 패턴을 통해 자바에서도 조금 더 그럴싸하게 함수형 패턴과 비동기 패턴의 코드를 작성할 수 있었습니다.</p>
</blockquote>
<p>온라인 과정의 마지막 활동으로 팀단위 활동이 있습니다.
교육과정 동안 3, 4 번 팀을 형성하게 됩니다. 그리고 한 팀 당 4 ~ 6 명의 인원이 배정됩니다. 강의나 개인 프로젝트를 진행할 때도 언제 팀이 존재하는데 팀 단위로 수업 내용을 복습하고 수업 중에 나오거나 좀 더 심화된 주제로 깊게 조사해 발표하는 과정도 매주 가집니다. (이 주제는 강사분께서 제시해주시는데 매번 주제가 만족스러워 좋은 평가를 받았습니다.) 뿐만 아니라 팀끼리 면접 스터디도 진행하며 팀 활동의 혜택을 많이 누릴 수 있었습니다.
이 부분은 저같이 혼자서 공부해온 사람에게 좋은 기회가 되었습니다. 저와 같이 다른 사람들과 네트워킹을 가지기 힘든 사람에겐 좋을 수 있어 보입니다.</p>
<p>그리고 이 부트캠프의 백미라 할 수 있는 네트워킹 데이가 존재합니다.
온라인에서만 만났던 동료, 강사분들을 오프라인에서 만나 친목을 도모하고 카카오, 구름에서의 연사분들이 오셔서 개발자 그리고 직장인으로써의 지식, 마인드셋 같은 것을 강연해주십니다. 오프라인에서 코딩테스트도 진행하는데 이것도 유익한 경험이였습니다. 이후 회식자리를 가지는데 팀원분들 그리고 다른 동료분들과 친해질 기회가 생겨 좋았습니다. (운영은 구름쪽에서 맡아주시는데 이런 오프라인 행사에선 특히 고생을 많이 해주셨습니다.)</p>
<p>온라인 과정동안엔 크게 이 정도가 있었던것 같습니다.</p>
<p>적고나니 정말 빠르게 흘러갔습니다.
흠..</p>
<hr>
<h3 id="대망의-오프라인">대망의 오프라인</h3>
<p>오프라인 과정을 요약하자면 팀 프로젝트를 하는데 스타트업 컨셉으로 진행하며 에자일 프로세스를 지향해 빠르게 피드백을 받고 빠르게 의견을 정해 진행합니다.
모든 기획과 프로젝트 세팅, 배포 및 운영을 전부 경험하게 되며 늘 팀원과 의견을 나누고 서로를 설득해 팀이 나은 결정을 할 수 있도록 시간을 투자해야했습니다.</p>
<p>그리고 오프라인 기간 동안엔 카카오 현직자 분이 멘토로 모든 팀에 배정이 되는데
매주 멘토링을 진행하며 프로젝트 및 취준 관련 준비를 직접 도와주십니다. 이게 진짜 좋습니다. 기술적인 조언도 많이 해주시고 질문에 대한 답변도 만족스러워 유익하다는 생각을 많이 하게 되었습니다. </p>
<p>이렇게 프로젝트를 진행하면 1차, 2차 출시를 하게 되는데 직접 출시한 서비스를 외부 사람들에게 홍보하게 됩니다. 이때 실제 사용자들의 트래픽을 받을 수 있는데 저같이 개발만 해보고 운영은 못해본 사람에겐 정말 유익한 경험인 것 같습니다. 여태껏 간과해왔던 운영관점의 로깅 정책과 모니터링 툴도 고민하고 적용했는데 이런 경험을 원했던 사람에겐 참 좋은 과정인 것 같습니다.</p>
<p>오프라인 과정에선 프로젝트 뿐만 아니라 코딩 테스트 준비, 면접, 이력서, 포트폴리오 관리 등을 정규시간에 하게 됩니다. 그리고 정규시간 외 저녁이나 이른 아침에 기술이나 면접 스터디를 진행하기도 합니다.</p>
<p>오프라인 과정에선 이러한 과정도 유익하지만 특히나 우수한 것은 강사님께서 본인만의 필살기를 전수해주시는 부분입니다. 자세한 내용은 알려드리기 조심스러운데 전 이 부분이 꽤 만족스럽습니다.</p>
<p>사실 오프라인 과정의 첫 주에는 해커톤을 진행한다던가 하는 시간이 있고 멘토분들과 오프라인에서 만날땐 네트워킹 데이를 가져 저녁에 회식자리를 가지기도 했습니다. 제주에서 생활을 하다보니 쉬는 날에는 다같이 여행을 간다던가 하기도 합니다. 전체적으로 공부도 열심히하고 쉴때는 쉬는 것도 잘한다는 느낌입니다.</p>
<hr>
<h3 id="오프라인-생활은-어떤가">오프라인 생활은 어떤가</h3>
<p>제주도 참 좋은데 바람이 정말 많이 붑니다. 가벼운 옷들을 주로 가져왔는데 두꺼운 외투를 두 달 동안 계속 입고 있습니다. 어느 곳을 가나 한라산이 보여 뭔가 자연의 정기가 느껴집니다..경사가 완만한데 산이 꽤 높아 정말 아름답습니다. 겨울이 살짝 끼면 한라산 정상에 눈이 쌓이는데 정말 아름답습니다.</p>
<p>숙소는 호텔에서 2인 1실을 제공해주는데 시설도 괜찮고 둘이서 사용하기에 적당하고 좋습니다. 생활하면서 불편한 점은 운영측에서 잘 관리해주고 소통해주셔서 불편함 없이 지내고 있습니다.</p>
<hr>
<h3 id="어떤-점이-만족스러운가">어떤 점이 만족스러운가</h3>
<p>지금까지 혼자서 공부할 땐 알 수 없었던 현업자분들의 작업방식이나 사고를 알 수 있어 좋았습니다. 그들의 작업 방식이나 소프트스킬도 보고 배울 수 있어 좋았습니다.
저처럼 시골에 사는 사람은 공부를 할때도 인프라가 적절하지 않을 수 있는데 여기선 숙소도 제공해주고 같이 공부할 사람도 있으니 좋았습니다.
취준생이라면 수입이 없다는게 부담스러울 수 있는데 월마다 들어오는 지원금이 정말 유용했습니다.
혼자서 취준을 하게되면 사람과 만났을때의 감이 좀 떨어질 수 있는데 그런 부분을 해소할 수 있어 좋았습니다.
맥북도 대여해주는데 좋았습니다. 그리고 웰컴키트도 구성품이 참 알뜰합니다.
제주에 오면 웰컴키트를 주시는데 크록스, 스테인리스 물병, 메신저백?(노트북 하나 들어가는 크로스백 느낌) 들이 있습니다. 정말 유용해서 두 달 내내 사용했으며 수료 후에도 잘 쓸 것 같습니다.</p>
<hr>
<h3 id="어떤-점이-아쉬운가">어떤 점이 아쉬운가</h3>
<p>딱히 아쉬운 점이 크게 없습니다.. 모든 위치의 사람들이 다 최선을 다 해주시는거 같습니다. 굳이 아쉬운 점이 있다면 수강생들 간 프론트, 백엔드, 데브옵스 지원율이 좀 극단적인 경향을 보여 팀빌딩 시 어려움을 겪었다는 점입니다. 요 부분은 기수별로 다른 경향을 보인다고 합니다.</p>
<br>

<h2 id="제주도">제주도</h2>
<p>하나 노파심에 말씀드리자면 두 달 동안 제주도는 볼 것도 많고 즐길 것도 많습니다. 이런 제주도가 누군가에겐 좋은 경치 구경하며 즐길 것도 즐기고 자신의 목표를 달성하기 위한 장소로 여겨질 수 있고 다른 누군가에겐 자신의 목표만을 달성하기 위해 순수하게 몰입하기위한 고립된 장소처럼 여겨질 수 있습니다.
전 후자에 가까웠습니다. 하지만 과정을 진행하며 여러 사람들이랑 생각을 공유하며 나의 생각이 언제나 옳은건 아니구나 같은 점도 배우고 여행도 같이 가고 하니 앞만 보고 달렸던 이전과 달리 주변도 볼 수 있고 새로운 방향으로도 나아갈 수 있었던 기회가 된것 같습니다. 전 이 경험이 꽤 마음에 드네요.</p>
<p>이런 합숙형 부트캠프의 장점은 다양한 목적과 성향의 사람들이 깊게 어우러질 수 있는 환경이라는 점 같습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 배치 필기노트]]></title>
            <link>https://velog.io/@ziggy_stardust/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B0%B0%EC%B9%98</link>
            <guid>https://velog.io/@ziggy_stardust/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B0%B0%EC%B9%98</guid>
            <pubDate>Sat, 22 Mar 2025 03:49:58 GMT</pubDate>
            <description><![CDATA[<p>패스트캠퍼스 스프링 배치 관련 강의를 듣고 개인적으로 정리한 글입니다.</p>
<p>현재 시점에서 필요하지 않는 기술로 판단되어 추가 공부는 보류</p>
<h2 id="배치란">배치란</h2>
<p>배치 작업 예시</p>
<ul>
<li>예약 시간에 광고성 메시지 발송</li>
<li>결제 정산 작업</li>
<li>운영을 위해 필요한 통계 데이터 구축</li>
<li>대량 데이터를 필요로 하는 모델 학습 작업</li>
</ul>
<p>배치는 <strong>실시간성이 아닌 데이터를 일괄처리</strong>하는 작업</p>
<p>작업 특성 상 일괄적으로 해야할 수 있다.</p>
<p>자원을 효율적으로 사용할 수 있는 것도 이유 중 하나이다.</p>
<p>주간, 월간 보고를 해야한다면 실시간으로 매번 그 데이터들을 처리하기 보단 주나 월마다 일괄적으로 데이터 처리하는게 나을 수 있다. (데이터 처리 시에만 고성능 하드웨어를 빌리면 된다.)</p>
<p>하나의 예시로 100만건 수준의 데이터를 Export 해줘야할 때 이 데이터 csv 로 만들어준다고 유저가 아무것도 못하게 하면 안된다. 그래서 백그라운드로 처리한 뒤 완료되면 사용되게끔 할 수 있다.</p>
<hr>
<p>스케쥴링과 배치 개념도 좀 잘 구분해야한다.</p>
<p>만약 서버에서 배치 하나만 돌릴 수 있고 그 하나의 작업이 너무 오래 걸린다면 내가 추가하고싶은 작업은 실행하지도 못한다.</p>
<p>그래서 작업이 끝나고 예약해서 작업을 할 수 있게도 할 수 있다.</p>
<h2 id="스프링-배치">스프링 배치</h2>
<ul>
<li><p>스프링 배치를 쓰는 이유는 스프링에서 제공하는 특성을 그대로 쓸 수 있기 때문이다.</p>
</li>
<li><p>스프링, 자바다 보니 플랫폼 종속적이지 않고 스프링의 DI를 이용한 객체결합 구성, AOP 를 이용한  부가적인 역할 분리 등 장점을 가져온다. 테스트에서도 잘 지원한다.</p>
</li>
<li><p>데이터 신뢰성도 높다 스프링 배치에는 다양한 데이터 수치가 있는데 우리 로그와 그 수치들을 합쳐서 어떠한 이유로 이런 데이터 결과가 나왔는지 확인이 가능하다. 이런 걸 통해 데이터 신뢰성을 지킬 수 있다.</p>
</li>
<li><p>배치와 스케쥴러는 다른 개념</p>
</li>
<li><p>특정 시간, 이벤트에 실행되는 방식은 없어 쿼츠 같은 걸로 스프링 기능으로 할 순 있지..</p>
</li>
<li><p>세 가지 레이어 존재</p>
</li>
<li><p>Application : 사용자 코드 및 구성</p>
</li>
<li><p>배치 처리를 위한 사용자의 코드 (배치 코어, 인프라도 사용자가 커스텀 할 수 있어)</p>
<ul>
<li>Core : Job, Step, JobLauncher, JobParameter</li>
<li>Infrastructure : File, DB (데이터를 읽고 쓸 수 있는 리더 라이터를 가진다.)</li>
</ul>
</li>
</ul>
<hr>
<h2 id="설계">설계</h2>
<p>각 피쳐들이 있고 이 피쳐들은 잡이라 부른다.
잡은 스텝들로 구성된다.</p>
<h3 id="스텝">스텝</h3>
<p>배치 처리 정의하고 제어하는 독립 작업 단위
tasklet step : 간단히 정의한 하나의 작업 단위
ex 데이터 하나를 삭제한다 같이</p>
<p>chunk-oriented step : 한 번에 하나씩 데이터를 읽고 chunk 를 만든 후 chunk 단위로 트랜잭션을 처리</p>
<p>아이템 단위로 한다네.. ItemReader 로 읽고 ItemProcessor로 처리하고 ItemWriter 로 저장하고 etl 그런건가
아이템 프로세서는 필수 요소 아니라 괜찮다하고
아이템리더로 하나씩 읽고 읽고 라이터로 결과 뱉네</p>
<p>리드, 프로세스, 라이터를 일괄적으로 처리된다.
청크 기반은 세 가지 루프가 있는데 리드에서 읽는거 프로세스에서 처리하는거 그리고 이 전체 과정을 마지막 루프로..</p>
<p>청크 단위로 트랜잭션이 구성되어 커밋과 롤백 발생</p>
<h3 id="잡">잡</h3>
<p>처음부터 끝까지 독립적이며 스탭들로 이루어진 집합, 유일하고 여러 순서가 있는 스텝들로 이루어짐. 외부 영향 받지 않고 실행</p>
<p>Job Repository 는 RDB 로 배치수행 관련 데이터 저장 (시간, 읽기 쓰기 횟수, 상태)</p>
<p>Job Launcher 에서 Job 을 실행. 현재 쓰레드 이용할지 풀 이용할지 파라미터 유효한지 검색.</p>
<p>잡 런처로 잡 실행하면 스텝이 돌아간다.</p>
<h3 id="jobrepository">JobRepository</h3>
<p>BATCH_JOB_INSTANCE : 배치 첨  시작하면 단일 잡 인스턴스가 등록</p>
<p>BATCH_JOB_EXECUTION : 배치 시작하고 실행 결과를 꾸준히 생성한다. 언제 시작하고 끝났으며 상태를 자꾸 반영한다.</p>
<p>BATCH_JOB_EXECUTION_CONTEXT : 리트라이 시도같은거 하떄 유용한 값들 배치 재시작.. 여러번 실행할 떄 유용한</p>
<p>BATCH_JOB_EXECUTION_PARAMS : 잡에서 사용된 파라미터들을 저장</p>
<p>BATCH_STEP_EXECUTION : 배치에 사용되는 스텝들의 상태를 저장, 데이터 읽고 쓰고 건너띄고 그런걸 기록</p>
<p>BATCH_STEP_EXECUTION_CONTEXT : 리트라이나 여러번 재시도할 때 유용</p>
<p>RDB 말고 인메모리로도 가능</p>
<hr>
<p>이용권 만료는 이용권 읽어서 만료된거 write 해주면 끝
청크기반으로 구성했다.</p>
<p>회원에게 일괄 지급하는건. 단순해서 데이터를 읽어서 일괄지급하는 청크 방식으로도 구성할 수 있지만, Tasklet 로 한 번 해보자.</p>
<p>병렬처리.. 확장이 편하게 해보자..
다중 쓰레드 청크를 사용해 오른쪽 세 개 청크.. 청크기반은 청크한테 트랜잭션을 먹고 쓰레드로 작업해.. 병렬로 ..
멀티 쓰레드를 쓰면 동시성 이슈.. 레이스 컨디션..</p>
<p>청크 방식인데 비동기로 진행한다. ItemProcessor 수학 복잡한거라던가 시간 걸리 API 쓰면 퓨처로 반환하고 완료가 되면 writer 한테 전달되어 실행 위임</p>
<p>통계 데이터만들고 파일로 반환.</p>
<p>원래 청크하나 있고 다음 스탭 나아갔는데 두 개의 스탭으로 간다. 앞에서 청크를 병렬적으로했는데 여기선 스탭을 병렬로 한다. (독립적이단 가정하에)</p>
<p>일간, 주간 보고서 작업..</p>
<hr>
<h2 id="itemreader">ItemReader</h2>
<p>대용량 데이터 다루는건 흔한 작업. 한 번에 다 가져오면 안되기에
커서, 페이징으로 접근한다.</p>
<p>읽고 처리하고 읽고 처리하고 이거 청크 사이즈 까지 만들어서
라이팅.</p>
<h3 id="cursor">Cursor</h3>
<p>Cursor ItemReader -&gt; </p>
<ul>
<li>java.sql.ResultSet</li>
<li>Database와 Connection 을 맺은 후 한 번에 하나씩 레코드를 Streaming 하며 다음 레코드로 진행한다. (Cursor 를 움직인다.)</li>
<li>JdbcCursorItemReader</li>
<li>HibernateCursorItemReader</li>
<li>JpaCrsorItemRader ..</li>
</ul>
<p>커넥션 맺어서 하나씨 레코드를 스트리밍</p>
<h3 id="page">Page</h3>
<p>청크 사이즈만큼 데이터를 가져온다.</p>
<p>커서는 하나의 커넥션으로 동작하기에 빠를 수 있다.
수행 시간이 오래걸리면 커넥션이 끊길 수 있다.</p>
<p>페이지는 한 청크 가져올때마다 커넥션을 생성하기에 커서보다 좀 늦을 수 있다. 안정적일 순 있다. </p>
<h2 id="itemwriter">ItemWriter</h2>
<p>건건이 저장하는게 아니라 청크 단위로 진행 그래서 입력을 List 로 받는다.</p>
<h2 id="만료-pass-상태-처리">만료 pass 상태 처리</h2>
<p>청크로 가져와서 처리는 커서로 하는데 왜 그러느냐
status 때문에.. 처리 과정 중에 status 에 변경을 주게되는데 페이징으로 가져오면 데이터가 누락되는 경우가 있어
그래서 데이터 변경에 무관한 무결성 조회가 가능한 커서로 읽어온다.</p>
<p>JPACursorItemReader 는 Spring4.3 에 추가되었다한다.</p>
<p>스텝 구성을 참 재밌게 하시네.
스텝이 청크로 구성하셨는데 청크 단위로 했다가 
내부에 리더를 박았는데 얘는 또 커서라네 뭐 우째되는기고</p>
<hr>
<p>Tasklet execute 구현</p>
<p>tasklet 에 execute 반환은 RepeatStatus 로 이걸로 반복 가능</p>
<ul>
<li>Spring Batch 는 Step 과 그 하위 Chunk의 반복 작업이며 Repeat 정책을 따른다.</li>
</ul>
<p>Job -&gt; Step -&gt; [Tasklet -&gt;[Chunk]]</p>
<p>name | desc
CONTINUABLE - 처리를 계속할 수 있음, Spring Batc에게 해당 Tasklet을 다시 실행하도록 정의
FINISHED - 처리가 완료되었음, 처리의 성공 여부에 관계없이 Tasklet의 처리를 완료하고 다음 처리를 진행</p>
<hr>
<p>기본적으로 스텝은 단일 쓰레드로 진행된다. 직렬화란 얘기인듯</p>
<p>멀티스레드 스텝은 각 청크를 자바의 테스크익스큐터를 통해 멀티스레드로 실행하는 방식.
배치의 리더, 라이터가 쓰레드세이프한지도 봐야한다. </p>
<p>각 스탭</p>
<p>커서는 낫 스레드세이프..
페이지는 변경 잡업에 못써..
그래서 싱크로나이즈는 스트림리더 써..
라이트는 코스트가 많이들어 멀티쓰레드 좋아..</p>
<p>스텝에서 멀티스레드 결정 짓는구나..
이후 리더에선 커서로 할 경우 싱크로나이즈..</p>
<p>프로세서 그런 애들한테도 내부적으로 지정이 가능하다 TaskExecutor를 .. 이걸로 멀티스레드 처리
위임을 해버린다. 다른 스레드에게..</p>
<hr>
<h2 id="bean-scope-개념">Bean Scope 개념</h2>
<p>기본적으로 스프링 빈 스코프는 싱글톤
여기서 하려는건 빈 생성 시점을 지정된 Scope가 명시된 method가 실행된 시점으로 지연시키는 것</p>
<ul>
<li>JobScope 잡이 실행될 때 생성되고 끝날 때 삭제되도록</li>
<li>StepScope 스텝이 실행될 때 생성되고 끝날 때 삭제
Why</li>
</ul>
<ol>
<li>JobParameter 를 method 실행하는 시점까지 지연시켜 할당할 수 있다.</li>
<li>동일한 Component 를 병렬로 처리할 때 안전할 수 있다. (돌려쓰지 않고 매번 자체 생산해서 사용한다.)</li>
</ol>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Rate Limiter 만들기]]></title>
            <link>https://velog.io/@ziggy_stardust/Rate-Limiter-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@ziggy_stardust/Rate-Limiter-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 16 Feb 2025 08:24:56 GMT</pubDate>
            <description><![CDATA[<p>가상 면접 사례로 배우는 대규모 시스템 설계 기초 책을 보고 만든 처리율 제한기</p>
<h1 id="1-fixed-window-counter-고정-윈도우-카운터">1. Fixed Window Counter (고정 윈도우 카운터)</h1>
<p>개념
    •    일정한 시간 간격 (예: 1초, 10초, 1분 등)마다 요청 카운트를 초기화하는 방식.
    •    매우 단순하며 메모리 사용량이 적고 속도가 빠름.
    •    하지만 <strong>시간 경계 문제(Time Boundary Issue)</strong>가 발생할 수 있음.</p>
<p>구현 방식
    1.    ConcurrentHashMap&lt;String, Integer&gt;를 사용하여 클라이언트의 요청 횟수를 저장.
    2.    AtomicInteger를 사용하여 카운터를 업데이트.
    3.    일정 시간이 지나면 카운터를 초기화.</p>
<pre><code class="language-java">import java.time.Clock;
import java.time.Instant;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class FixedWindowRateLimiter {
    private final int maxRequests;
    private final long windowMillis;
    private final Map&lt;String, AtomicInteger&gt; requestCounts = new ConcurrentHashMap&lt;&gt;();
    private volatile long windowStart;
    private final Clock clock;

    public FixedWindowRateLimiter(int maxRequests, long windowMillis, Clock clock) {
        this.maxRequests = maxRequests;
        this.windowMillis = windowMillis;
        this.clock = clock;
        this.windowStart = clock.millis();
    }

    public synchronized boolean allowRequest(String clientId) {
        long now = clock.millis();

        // 윈도우 초기화
        if (now - windowStart &gt;= windowMillis) {
            requestCounts.clear();
            windowStart = now;
        }

        // 요청 카운트 증가
        requestCounts.putIfAbsent(clientId, new AtomicInteger(0));
        return requestCounts.get(clientId).incrementAndGet() &lt;= maxRequests;
    }
}
</code></pre>
<p>장점 &amp; 단점</p>
<p>장점
    •    구현이 단순하고 메모리 사용량이 적음.
    •    매우 빠른 속도 (AtomicInteger 사용).
    •    트래픽이 균등할 경우 매우 효과적.</p>
<p>단점
    •    시간 경계 문제: 시간이 갱신되면 카운트가 리셋되므로, 한 윈도우의 끝과 다음 윈도우의 시작 부분에서 몰아치는 트래픽을 허용할 수 있음.
→ 예: 10초 동안 10개 제한인데, 9.9초에 10개 + 10.1초에 10개 = 20개 요청 허용될 수 있음.</p>
<hr>
<h1 id="2-sliding-window-counter-슬라이딩-윈도우-카운터">2. Sliding Window Counter (슬라이딩 윈도우 카운터)</h1>
<p>개념
    •    Fixed Window 방식의 단점을 개선한 방식.
    •    일정 시간 간격을 작은 슬롯(slot)으로 나누고, 요청이 들어올 때마다 최신 슬롯을 업데이트.
    •    트래픽이 몰리는 문제를 완화하면서도 Sliding Window Log보다 메모리 효율적.</p>
<p>구현 방식
    1.    ConcurrentHashMap&lt;String, Deque&lt;Integer&gt;&gt;를 사용하여 요청 수를 슬롯 단위로 저장.
    2.    오래된 슬롯을 제거하고 새로운 슬롯을 추가.
    3.    요청이 들어오면, 최근 N초 동안의 총 요청 수를 계산.</p>
<pre><code class="language-java">import java.time.Clock;
import java.time.Instant;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class SlidingWindowCounterRateLimiter {
    private final int maxRequests;
    private final long windowMillis;
    private final int slotCount;
    private final Map&lt;String, Deque&lt;Integer&gt;&gt; requestCounts = new ConcurrentHashMap&lt;&gt;();
    private final Clock clock;

    public SlidingWindowCounterRateLimiter(int maxRequests, long windowMillis, int slotCount, Clock clock) {
        this.maxRequests = maxRequests;
        this.windowMillis = windowMillis;
        this.slotCount = slotCount;
        this.clock = clock;
    }

    public synchronized boolean allowRequest(String clientId) {
        long now = clock.millis();
        requestCounts.putIfAbsent(clientId, new LinkedList&lt;&gt;());

        Deque&lt;Integer&gt; slots = requestCounts.get(clientId);

        // 오래된 슬롯 제거
        while (slots.size() &gt;= slotCount) {
            slots.pollFirst();
        }

        // 현재 슬롯 증가
        if (slots.isEmpty() || now - slots.peekLast() &gt; windowMillis / slotCount) {
            slots.addLast(1);
        } else {
            slots.addLast(slots.pollLast() + 1);
        }

        return slots.stream().mapToInt(Integer::intValue).sum() &lt;= maxRequests;
    }
}</code></pre>
<p>장점 &amp; 단점</p>
<p>장점
    •    Fixed Window보다 트래픽이 몰리는 문제를 완화.
    •    Sliding Window Log보다 메모리 사용량이 적음.</p>
<p>단점
    •    요청 개수를 합산해야 하므로 연산 비용이 증가.</p>
<hr>
<h1 id="3-token-bucket-토큰-버킷">3. Token Bucket (토큰 버킷)</h1>
<p>개념
    •    N개의 토큰을 가진 버킷을 생성하고, 요청이 올 때마다 토큰을 하나씩 사용.
    •    버킷이 비어있으면 요청 차단, 일정 시간마다 토큰을 일정 개수만큼 추가.</p>
<p>구현 방식
    1.    AtomicInteger를 사용하여 현재 토큰 개수를 저장.
    2.    주기적으로 일정 개수의 토큰을 추가 (ScheduledExecutorService 사용).
    3.    요청이 들어오면 토큰이 남아있는지 확인 후 요청을 허용.</p>
<pre><code class="language-java">import java.time.Clock;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class TokenBucketRateLimiter {
    private final int maxTokens;
    private final int refillRate;
    private final AtomicInteger availableTokens;
    private final Clock clock;

    public TokenBucketRateLimiter(int maxTokens, int refillRate, Clock clock) {
        this.maxTokens = maxTokens;
        this.refillRate = refillRate;
        this.availableTokens = new AtomicInteger(maxTokens);
        this.clock = clock;

        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(this::refillTokens, 1, 1, TimeUnit.SECONDS);
    }

    private void refillTokens() {
        availableTokens.updateAndGet(current -&gt; Math.min(maxTokens, current + refillRate));
    }

    public boolean allowRequest() {
        return availableTokens.getAndDecrement() &gt; 0;
    }
}</code></pre>
<p>장점 &amp; 단점</p>
<p> 장점
    •    트래픽이 갑자기 몰리는 문제를 완화하면서도, 일정 속도로 요청을 허용할 수 있음.
    •    네트워크 대역폭 제한, API Rate Limiting에 가장 적합.</p>
<p> 단점
    •    구현이 조금 복잡하고, ScheduledExecutorService를 사용해야 함.</p>
<hr>
<h3 id="어떤-기법을-선택해야-할까">어떤 기법을 선택해야 할까?</h3>
<table>
<thead>
<tr>
<th>기법</th>
<th>성능</th>
<th>정확도</th>
<th>메모리 사용</th>
<th>추천 사용 사례</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Fixed Window</strong></td>
<td>빠름</td>
<td>낮음 (경계 문제 발생)</td>
<td>낮음</td>
<td>단순한 요청 제한</td>
</tr>
<tr>
<td><strong>Sliding Window Counter</strong></td>
<td>중간</td>
<td>높음</td>
<td>중간</td>
<td>실시간 API Rate</td>
</tr>
<tr>
<td><strong>Sliding Window Log</strong></td>
<td>느림</td>
<td>높음</td>
<td>높음</td>
<td>매우 정확한 요청 제한 필요할 때</td>
</tr>
<tr>
<td><strong>Token Bucket</strong></td>
<td>빠름</td>
<td>높음</td>
<td>낮음</td>
<td>네트워크 트래픽 제한</td>
</tr>
</tbody></table>
<p>결론
    •    성능이 가장 중요하면 Fixed Window 또는 Token Bucket 추천.
    •    트래픽이 몰리는 문제가 중요하면 Sliding Window Counter 추천.
    •    정확성이 중요한 경우 Sliding Window Log.</p>
<p>네트워크 요청을 안정적으로 처리하려면 Token Bucket이 가장 추천</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Query와 상태관리 :: 2월 우아한테크세미나 정리]]></title>
            <link>https://velog.io/@ziggy_stardust/React-Query%EC%99%80-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-2%EC%9B%94-%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%84%B8%EB%AF%B8%EB%82%98-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@ziggy_stardust/React-Query%EC%99%80-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-2%EC%9B%94-%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%84%B8%EB%AF%B8%EB%82%98-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sun, 02 Feb 2025 19:08:34 GMT</pubDate>
            <description><![CDATA[<p>다음 영상을 보고 정리했습니다. 
<a href="https://www.youtube.com/live/MArE6Hy371c?si=G6BvsSuU_kvFHyW3"> React Query와 상태관리 :: 2월 우아한테크세미나</a></p>
<h3 id="상태">상태</h3>
<p>주어진 시간에 대해 시스템을 나타내는 것으로 언제든지 변경될 수 있다.</p>
<p><em>개발자에겐 관리해야할 데이터</em>
<br></p>
<h3 id="생태계-흐름">생태계 흐름</h3>
<p>프론트엔드라는 분야가 구분될만큼 사용자에게 보여지는 부분이 많아졌다. </p>
<p><em>관리해야할 상태가 더 많아짐</em></p>
<p>안그래도 계속해서 변경되는 상태가 양까지 많아졌다.
<br></p>
<h3 id="마주친-문제">마주친 문제</h3>
<p>연사님이 당시에 마주한 문제는 <strong>전역 상태 Store 에서 상태관리보다는 API 통신 코드가 많았다</strong>는 것이다.</p>
<ul>
<li>API 통신 관련 코드가 Store 에 존재</li>
<li>반복되는 isFetching, isError 등 API 관련 상태<br>

</li>
</ul>
<h3 id="서버로부터-받는-상태">서버로부터 받는 상태</h3>
<p>이러한 현상을 유발하는 상태들의 특성을 다시 정리해야했다.</p>
<ul>
<li>Client 에서 제어하지 못하고 소유하지 못한 원격 장소에서 관리되고 유지됨</li>
<li>이 상태를 가져오기 위해 비동기 API 호출이 필요함 (Fetching, Updating)</li>
<li>사용자가 모르는 사이에 변경될 가능성이 존재 (관리되는 쪽이 오픈된 원격)</li>
<li>신경 쓰지 않는다면 잠재적으로 out of date 가 될 가능성이 존재 </li>
</ul>
<p>이래서 원격으로부터 받아온 상태는 일종의 캐시처럼 존재한다.
<br></p>
<h3 id="클라이언트-상태와-서버-상태">클라이언트 상태와 서버 상태</h3>
<p>상태의 소유권으로 상태를 구분할 수 있게 되었다.
그 특성을 정리하자면 아래와 같다.</p>
<p><strong>클라이언트 상태</strong> (소유권이 클라이언트에게)</p>
<ul>
<li>클라이언트에서 소유하며 온전히 제어가능</li>
<li>초기값 설정이나 조작에 제약사항 없음</li>
<li>다른 사람들과 공유되지 않으며 클라이언트 내에서 UI/UX 혹은 사용자 인터렉션에 의해 변경될 수 있음</li>
<li>항상 최신 상태로 관리됨<br>

</li>
</ul>
<p><strong>서버 상태</strong> (소유권이 서버에게)</p>
<ul>
<li>원격에서 상태가 관리됨</li>
<li>상태를 가져오기 위해 비동기 API 호출이 필요</li>
<li>사용자가 모르게 원본 상태가 변경될 수 있음</li>
<li>out of date 가능성이 높음</li>
</ul>
<br>



<h3 id="react-query">React Query</h3>
<p>이러한 문제를 해결하기 위해 나온 라이브러리가 <strong>React Query</strong></p>
<p>React Query 홈페이지에선 다음과 같이 설명한다.</p>
<blockquote>
<p><em>Performant and powerful data synchronizatioiin for React</em> <br>
효과적이고 강력한 데이터 동기화툴 <br>
<em>Fetch, cache and update data in your React and React Native applications all without touching any &quot;global state&quot;</em> <br>
리액트 환경에서 전역 상태를 건드리지 않고 캐시를 동기화한다.<br></p>
</blockquote>
<p>원격에 있는 상태를 클라이언트와 동기화시켜주는 기술이라 볼 수 있다.</p>
<p>특징적으로는 선언적이며 React Hook 과 유사한 인터페이스로 친숙함, 그리고 Zero Configuration 으로 간편하고 기능적으로 강력하다.
<br></p>
<p><strong>공식 예제 코드</strong></p>
<pre><code class="language-typescript">import {
  QueryClient,
  QueryClientProvider,
  useQuery,
} from &#39;@tanstack/react-query&#39;

const queryClient = new QueryClient()

export default function App() {
  return (
    &lt;QueryClientProvider client={queryClient}&gt;
      &lt;Example /&gt;
    &lt;/QueryClientProvider&gt;
  )
}

function Example() {
  const { isPending, error, data } = useQuery({
    queryKey: [&#39;repoData&#39;],
    queryFn: () =&gt;
      fetch(&#39;https://api.github.com/repos/TanStack/query&#39;).then((res) =&gt;
        res.json(),
      ),
  })

  if (isPending) return &#39;Loading...&#39;

  if (error) return &#39;An error has occurred: &#39; + error.message

  return (
    &lt;div&gt;
      &lt;h1&gt;{data.name}&lt;/h1&gt;
      &lt;p&gt;{data.description}&lt;/p&gt;
      &lt;strong&gt;👀 {data.subscribers_count}&lt;/strong&gt;{&#39; &#39;}
      &lt;strong&gt;✨ {data.stargazers_count}&lt;/strong&gt;{&#39; &#39;}
      &lt;strong&gt;🍴 {data.forks_count}&lt;/strong&gt;
    &lt;/div&gt;
  )
}</code></pre>
<br>

<p>React Query 를 사용하려면 <strong>QueryClientProvider</strong> 는 필수</p>
<pre><code class="language-typescript">import {
  QueryClient,
  QueryClientProvider,
  useQuery,
} from &#39;@tanstack/react-query&#39;

const queryClient = new QueryClient()

export default function App() {
  return (
    &lt;QueryClientProvider client={queryClient}&gt;
      &lt;Example /&gt;
    &lt;/QueryClientProvider&gt;
  )
}</code></pre>
<br>

<h3 id="core-concept">Core Concept</h3>
<ul>
<li><code>Queries</code></li>
<li><em>data fetching, 즉 Read 담당. Get API 에 자주 사용*</em>
<br>A query is a declarative dependency on an asynchronous source of data that is tied to a unique key. <br> <br> 라고 설명하는데 unique key 를 이용해 데이터를 비동기적으로 가져오는 의존성이라는 설명입니다. 서버로부터 값을 가져오기위한 Promise 기반 method 에 사용 가능하며 서버의 값에 수정을 가할 수 있는 작업은 Mutation 을 사용하길 권장합니다.<br>
Query 는 key, Value 구조를 연상할 수 있고 Query Key 에 따라 query caching 을 관리합니다.
Query Key 는 String, Array 형태로 존재할 수 있습니다.<br></li>
<li><em>useQuery 의 반환값*</em>
<code>data</code> : 마지막으로 성공한 resolved된 데이터 (Response)
<code>error</code> : 에러가 발생했을 떄 반환되는 객체
<code>isFetching</code> : Request 가 in-flight 중일 때 true
<code>status</code>, <code>isLoading</code>, <code>isSuccess</code>, <code>isLoading</code> 등 : 현재 query 의 상태
<code>refetch</code> : 해당 query refetch 하는 함수 제공
<code>remove</code> : 해당 query cache 에서 지우는 함수 제공
<code>기타 등등</code> : 이 있닷.<br></li>
<li><em>useQuery 의 세 번째 인자로 커스텀 옵션을 줄 수 있다.*</em>
<code>onSuccess</code>, <code>onError</code>, <code>onSettled</code> : query fetching 성공 / 실패 / 완료 시 실행할 Side Effect 정의
<code>enabled</code> : 자동으로 query 를 실행시킬지 말지 여부
<code>retry</code> : query 동작 실패 시 , 자동으로 ㄱetry 할지 결정하는 옵션
<code>select</code> : 성공 시 가져온 data 를 가공해 전달
<code>keepPreviousData</code> : 새롭게 fetching 시 이전 데이터 유지 여부
<code>refetchInterval</code> : 주기적으로 refetch 할지 결정하는 옵션 (폴링 시 유리)
<code>기타 등등</code> </li>
</ul>
<pre><code class="language-typescript">const todo = useQuery(&#39;todo&#39;, () =&gt; {
  return axios.get(&#39;/todos&#39;)
});</code></pre>
<ul>
<li><code>Mutations</code></li>
<li><em>Create, Update, Delete 를 위한 의존성*</em> <br>
Unlike queries, mutaions are typically used to create/update/delete data or perform server side-effects. For this purpose, React Query exports a useMutation hook. <br>
Query 와 달리 서버의 데이터 생성, 수정, 삭제를 위해 사용하는 의존성 <br>
key 필요 없이 호출 가능<br></li>
<li><em>반환값*</em>
<code>mutate</code> : mutation 을 실행하는 함수
<code>mutateAsync</code> : mutate 와 비슷하지만 Promise
<code>reset</code> : mutation 내부 상태 clear
나머지는 Query 와 비슷<br></li>
<li><em>옵션*</em>
<code>onMutate</code> : Mutation 동작 전 먼저 동작하는 함수, Optimistic update 적용 시 유용
나머지는 Query와 비슷<blockquote>
<p>Optimistic update 는 좋아요처럼 성공을 낙관적으로 판단해 API 응답 전에 성공 처리하는 기법 , 롤백 가능<br></p>
</blockquote>
</li>
</ul>
<pre><code class="language-typescript">const mutation = useMutation(newTodo =&gt; {
  return axios.post(&#39;/todos&#39;, newTodo)
});</code></pre>
<ul>
<li><code>QueryInvalidation</code>
queryClient 가 invalidate 메소드를 호출해서 처리
이러면 해당 Key 를 가진 query 는 stale 취급되고, 현재 rendering 되고 있는 query 들은 백그라운드에서 refetch 됩니다.</li>
</ul>
<pre><code class="language-typescript">// Invalidate every query in the cache
queryClient.invalidateQueries();
// Invalidate every query with a key that starts with `todos`
queryClient.invalidateQueries(&#39;todos&#39;)</code></pre>
<br>

<h3 id="caching-과-synchronization">Caching 과 Synchronization</h3>
<p>위 자료에선 Opton 에 cacheTime, staleTime 이 있었다.
refetchOnWindowFocus, refetchOnMount 같은 것도 있었다.</p>
<p>React Query 의 refetch 메커니즘은 RFC 5861 의 개념을 참고하고 있습니다.
RFC 5861 (HTTP Cache-Control Extensions for Stale Content)</p>
<ul>
<li>stale-while-revalidate
백그라운드에서 stale response 를 revalidate 하는 동안 캐시가 가진 stale response 를 반환<pre><code>Cache-Control: max-age=600, stale-while-revalidate=30</code></pre></li>
</ul>
<p>이렇게 동작하면 Latency(지연시간) 가 숨겨진다.
(stale-if-error 도 이 명세에 존재, 요건 실패 시 이전 값 제공)</p>
<p>요약하자면 max-age=600, stale-while-revalidate=30 인 경우 캐싱 중인 값이 600초가 넘으면 새로운 값을 가져오는데 이때 stale 기간 30초까진 이전 값을 제공하며 백그라운드에서 새롭게 값을 가져온다는 뜻입니다.
<br></p>
<p>이런 컨셉을 메모리 캐시에도 적용한다. (react-query, swr, etc)</p>
<ul>
<li><p>cacheTime : 메모리에 얼마만큼 있을건지 (해당 시간 이후 GC 에 의해 처리, default 5분)</p>
</li>
<li><p>staleTime : 얼마의 시간이 흐른 후에 데이터를 stale 취급할 것인지 (default 0)</p>
</li>
<li><p>refetchOnMount, refetchOnWindowFocus, refetchOnReconnect 들이 true 일 경우 Mount, window focus, reconnect 시점에 data 가 stale 이면 모두 refetch (모두 default true)</p>
<br>

</li>
</ul>
<h3 id="query-상태흐름">Query 상태흐름</h3>
<p><img src="https://velog.velcdn.com/images/ziggy_stardust/post/ba3addd9-0d46-4ccf-a286-40517828ef79/image.png" alt=""></p>
<p>활성화를 고려할 경우 (꺼졌다, 켜졌다하는 UI)
<img src="https://velog.velcdn.com/images/ziggy_stardust/post/1dd6fd57-91bd-4ea6-a41d-1123de5cd9c7/image.png" alt=""></p>
<p>표시되진 않을 때 inactive 가 되며 다시 활성화되면 상태에 따라서 동작합니다. stale 상태라면 refetch 가 동작할 수 있습니다.<br></p>
<h3 id="zero-config">zero-config</h3>
<p><strong>관련 기본값</strong>
<code>staleTime</code> -&gt; 0 (Queries 에서 cached data 는 언제나 stale 취급)
<code>refetchOnMount</code>, <code>refetchOnWindowFocus</code>, <code>refetchOnReconnect</code> -&gt; true (각 시점에서 stale 이라면 항상 refetch)
<code>cacheTime</code> -&gt; 60 * 5 * 1000 (inActive Query 들은 5분 뒤 GC 에 의해 처리)
<code>retry</code> -&gt; 3 (Query 실패 시 3번까지 retry)
<code>retryDelay</code> -&gt; exponential backoff function
<br></p>
<h3 id="궁금한-점">궁금한 점</h3>
<ul>
<li>Q ) 전역상태처럼 관리되는 것 같은데 Component A, Component B 에서 중복적으로 호출 시 어떻게 되나?
A ) 키가 동일하니 staleTime 이 유효하다면 refetch 동작을 안해 (뒤에 실행하는 게 무효화)<br></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java BigInterger 아주 간단한 사용법]]></title>
            <link>https://velog.io/@ziggy_stardust/Java-BigInterger-%EC%95%84%EC%A3%BC-%EA%B0%84%EB%8B%A8%ED%95%9C-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@ziggy_stardust/Java-BigInterger-%EC%95%84%EC%A3%BC-%EA%B0%84%EB%8B%A8%ED%95%9C-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Sat, 25 Jan 2025 17:58:32 GMT</pubDate>
            <description><![CDATA[<p><strong>BigInteger</strong> 는 자바에서 제공하는 라이브러리. native 자료형의 표현범위 한계를 해결하는 기능입니다.</p>
<ul>
<li>메모리가 지원하는한 숫자표현이 가능</li>
<li>다양한 기능을 제공</li>
<li>불변</li>
</ul>
<h3 id="메소드">메소드</h3>
<ul>
<li><code>생성</code><pre><code class="language-java">// 문자열
BigInteger bigInt = new BigInteger(&quot;12345678901234567890&quot;);
</code></pre>
</li>
</ul>
<p>// 바이트
import java.math.*;
public class BigIntegerEx {
  public static void main(String[] args) {
      byte[] bytes = {0x01, 0x00};
      BigInteger bigInt = new BigInteger(bytes);<br>      System.out.println(bigInt); // 256
  }
}</p>
<p>// native type
BigInteger bigInt = BigInteger.valueOf(12345L);</p>
<pre><code>
- `연산`
```java
    add(BigInteger val): 더하기
    subtract(BigInteger val): 빼기
    multiply(BigInteger val): 곱하기
    divide(BigInteger val): 나누기
    mod(BigInteger val): 나머지
    pow(int exponent): 거듭제곱
    abs(): 절댓값</code></pre><ul>
<li><code>변형</code><pre><code class="language-java">  toString(): 문자열로 변환
  intValue(), longValue(), floatValue(), doubleValue(): 기본 자료형으로 변환
  toByteArray(): 바이트 배열로 변환</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[하노이 탑과 재귀]]></title>
            <link>https://velog.io/@ziggy_stardust/%ED%95%98%EB%85%B8%EC%9D%B4-%ED%83%91%EA%B3%BC-%EC%9E%AC%EA%B7%80</link>
            <guid>https://velog.io/@ziggy_stardust/%ED%95%98%EB%85%B8%EC%9D%B4-%ED%83%91%EA%B3%BC-%EC%9E%AC%EA%B7%80</guid>
            <pubDate>Sat, 25 Jan 2025 17:06:06 GMT</pubDate>
            <description><![CDATA[<p>알아두면 좋은 점은 아래와 같다.</p>
<ol>
<li>모든 기둥은 항상 규칙을 준수한다. </li>
<li>목적은 자신을 목적지로 옮기고 싶어하고 그러기 위해 자신 위에 있는걸 목적지가 아닌 빈 곳으로 옮긴다.</li>
</ol>
<p><strong>시간 복잡도</strong>는 <strong>2^n</strong> 인데 재귀적으로 두 번 호출해서 그렇다고 생각하면 좋다.
세세하게는 점화식을 세워서 구할 수 있는데 스킵</p>
<p>하노이 탑이 유명하고 재귀공부할땐 필수적이지만 기억이 안나면 힘들다..</p>
<br>

<p><strong>하노이 탑 이동경로</strong></p>
<pre><code class="language-java">int rec(int from, int to, int val) {
    if (val == 1) {
        System.out.println(&quot;&quot; + from + &quot; &quot; + to + &quot;\n&quot;);
        return 1;
    }
    int empty = 6 - from - to;
    rec(from, empty, val - 1);
    System.out.println(&quot;&quot; + from + &quot; &quot; + to + &quot;\n&quot;);
    rec(empty, to, val - 1);
}</code></pre>
<br>

<p><strong>하노이 탑 특정 turn 에서 각 기둥의 합 구하기</strong></p>
<pre><code class="language-java">static int[] pillarSum = new int[4];
    static int cur = 0;
    static void rec(int from, int to, int val, int target) throws Exception {
        if (val == 1) {            
            pillarSum[from] -= val;
            pillarSum[to] += val;
            cur++;
            if (cur == target) {
                System.out.println(&quot;&quot; + pillarSum[1] + &quot; &quot; + pillarSum[2] + &quot; &quot; + pillarSum[3]);
                return;
            }
            return;
        }
        int nextEmpty = 6 - from - to;
        rec(from, nextEmpty, val - 1, target);
        pillarSum[from] -= val;
        pillarSum[to] += val;
        cur++;
        if (cur == target) {
            System.out.println(&quot;&quot; + pillarSum[1] + &quot; &quot; + pillarSum[2] + &quot; &quot; + pillarSum[3]);
            return;
        }
        rec(nextEmpty, to, val - 1, target);
    }

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        int t = Integer.parseInt(br.readLine());

        for (int i = 1; i &lt;= n; i++) {
            pillarSum[1] += i;
        }
        if (n &lt;= 20) {
            rec(1, 3, n, t);
        }
    }
}</code></pre>
<p>재귀를 잘하면 DP 에도 도움이 되니 이쪽도 공부를 해야겠다..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[트리의 지름]]></title>
            <link>https://velog.io/@ziggy_stardust/%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%A7%80%EB%A6%84</link>
            <guid>https://velog.io/@ziggy_stardust/%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%A7%80%EB%A6%84</guid>
            <pubDate>Sat, 18 Jan 2025 21:17:43 GMT</pubDate>
            <description><![CDATA[<p>트리에서 가장 긴 길이의 경로를 뜻한다. 이심률이라고도 하는 듯하다.</p>
<p>방법은 아래와 같다.</p>
<ol>
<li>임의의 정점(A)을 선택한다.</li>
<li>임의의 정점에서 가장 긴 정점(B)을 찾는다.</li>
<li>B 정점에서 가장 긴 정점(C) 를 찾는다.</li>
<li>B - C 의 경로가 가장 긴 경로이다.</li>
</ol>
<p>이를 통해 다시 느낄 수 있는 트리는 순환이 없다 그래서 간선의 수는 정점의 수 N - 1 이다.</p>
<p>위의 경우 각 거리의 비용(가중치)이 1이라는 가정하에 길이를 다뤘지만
문제에 따라 정점마다 점수를 줄 수도 있다. 이 경우엔 길이가 아닌 점수로 똑같이 적용하면 된다.</p>
<p>모든 점수가 양수라면 문제는 더 쉽다. 다익스트라처럼</p>
<pre><code class="language-java">import java.util.*;
import java.io.*;

public class 나무_위의_벌거지_2132 {
    public static List&lt;Integer&gt; lllog = new ArrayList&lt;&gt;();
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        int[] tree = new int[n];
        String[] inputs = br.readLine().split(&quot; &quot;);
        List&lt;ArrayList&lt;Integer&gt;&gt; path = new ArrayList&lt;&gt;(); // 반공변 한 번 더 체크
        for (int i = 0; i &lt; n; i++) {
            tree[i] = Integer.parseInt(inputs[i]);
            path.add(new ArrayList&lt;&gt;());
        }

        for (int i = 0; i &lt; n - 1; i++) {
            inputs = br.readLine().split(&quot; &quot;);
            int n1 = Integer.parseInt(inputs[0]) - 1;
            int n2 = Integer.parseInt(inputs[1]) - 1;
            path.get(n1).add(n2);
            path.get(n2).add(n1);
        }

        // 0 최대 합, 1 최대 노드
        int[] res = {-1, 10005};
        boolean[] visited = new boolean[n];
        visited[0] = true;
        dfs(res, tree, path, visited, 0, tree[0]);
        visited[0] = false;

        int start = res[1];
        res[0] = -1;
        res[1] = 10005;
        visited[start] = true;
        dfs(res, tree, path, visited, start, tree[start]);
        visited[start] = false;

        int last = res[1];
        int max = res[0];
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        bw.write(&quot;&quot; + max + &quot; &quot; + (Math.min(start, last) + 1));
        bw.flush();
    }

    public static void dfs(int[] res, int[] tree, List&lt;ArrayList&lt;Integer&gt;&gt; path, boolean[] visited, int cur, int curSum) {
        if (res[0] &lt; curSum || res[0] == curSum &amp;&amp; cur &lt; res[1]) {
            res[0] = curSum;
            res[1] = cur;
        }
        for (int next : path.get(cur)) {
            if (visited[next]) continue;
            visited[next] = true;
            dfs(res, tree, path, visited, next, curSum + tree[next]);
            visited[next] = false;
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[DNS (A 레코드, CNAME, TTL)]]></title>
            <link>https://velog.io/@ziggy_stardust/DNS-A-%EB%A0%88%EC%BD%94%EB%93%9C-CNAME-TTL</link>
            <guid>https://velog.io/@ziggy_stardust/DNS-A-%EB%A0%88%EC%BD%94%EB%93%9C-CNAME-TTL</guid>
            <pubDate>Wed, 15 Jan 2025 01:59:33 GMT</pubDate>
            <description><![CDATA[<p><code>A 레코드</code> : 도메인을 IPv4 에 매핑
<code>AAAA 레코드</code> : A 레코드의 IPv6 버전
<code>CNAME 레코드</code> : 도메인을 다른 도메인으로 리다이렉트
<code>TTL</code> : DNS 쿼리 결과 유효 시간 (너무 짧으면 쿼리를 자주 날리고 너무 길면 IP 변경 시 반영이 느림)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[배포 시 환경 변수 전달]]></title>
            <link>https://velog.io/@ziggy_stardust/%EB%B0%B0%ED%8F%AC-%EC%8B%9C-%ED%99%98%EA%B2%BD-%EB%B3%80%EC%88%98-%EC%A0%84%EB%8B%AC</link>
            <guid>https://velog.io/@ziggy_stardust/%EB%B0%B0%ED%8F%AC-%EC%8B%9C-%ED%99%98%EA%B2%BD-%EB%B3%80%EC%88%98-%EC%A0%84%EB%8B%AC</guid>
            <pubDate>Tue, 14 Jan 2025 02:48:35 GMT</pubDate>
            <description><![CDATA[<p>다양한 배포 전략이 있고 배포 도구가 있습니다.</p>
<p>이러한 다양한 환경에서 한 환경에만 가능한 종속되지 않게 환경 변수를 관리할 수 있을까 고민을 했습니다.</p>
<p>보안적인 부분도 고려를 해야했는데</p>
<p>이미지에 바로 기입해버리는건 리버싱 과정에서 뺴앗길 수 있어 하지않았습니다. 외부에서 주입을 받습니다. </p>
<p>그리고 배포를 도와주는 도구들이 공통적으로 가지는 수단은 배포스크립트를 짜게하는 것과 그 사이에 들어갈 수 있는 보안적인 요소들을 다뤄준다는 것. 이 두 가지가 핵심적입니다.</p>
<p>그리고 도커는 일반적으로 사용하기가 좋아 대부분 사용합니다.</p>
<hr>
<p>저의 경우 깃헙액션즈와 깃헙 컨테이너레지스트리를 통해서 배포를 진행하려합니다. 그리고 도커에 환경변수를 전달해야하는데 전 이런 환경변수를 깃헙 시큐어로 두지 않고 AWS Parameter Store 를 사용했습니다. aws 를 사용하는 환경에서 쉽게 사용하기 위함인데 사실 github actions 만 사용하는 환경에서 큰 문제는 없어보입니다. 이건 살짝 배포 환경을 바꿨을때 모든 시큐어 환경변수값을 하나하나 옮기는 게 비효율적여보여 택했습니다.</p>
<p>그래서 최소한의 시큐어 정보만을 배포 환경에 뒀습니다.</p>
<hr>
<p>그리고 ec2, lambda 에 SSM 관련 역할과 필요에 따라 aws-cli 를 둬버리면 배포가 가능합니다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS IAM 초기세팅]]></title>
            <link>https://velog.io/@ziggy_stardust/AWS-IAM-%EC%B4%88%EA%B8%B0%EC%84%B8%ED%8C%85</link>
            <guid>https://velog.io/@ziggy_stardust/AWS-IAM-%EC%B4%88%EA%B8%B0%EC%84%B8%ED%8C%85</guid>
            <pubDate>Sat, 11 Jan 2025 19:59:12 GMT</pubDate>
            <description><![CDATA[<h3 id="계정-id-별칭-생성">계정 ID 별칭 생성</h3>
<p>IAM 대시보드 우측에서 가능 </p>
<p><img src="https://velog.velcdn.com/images/ziggy_stardust/post/ce1ac26e-efe3-4741-8224-78252f6ef923/image.png" alt=""></p>
<h3 id="admin-생성">Admin 생성</h3>
<p>Root 는 절대 사용하지 않을 것</p>
<p><img src="https://velog.velcdn.com/images/ziggy_stardust/post/4cd6ebab-41e6-4c5f-a96e-e55cbaa14935/image.png" alt=""></p>
<p>권한 부여 시 직접 정책 연결로</p>
<p><code>AdministratorAccess</code> 추가</p>
<p>Admin 도 많은 권한을 가지기에 MFA 추가</p>
<p>지불에 관한 권한은 없음</p>
<p>root 의 계정 페이지에 들어가 
<code>결제 정보에 대한 IAM 사용자 및 역할 액세스</code> 를 활성화하면 된다.
결제 정보에 대한 권한을 주는건 아니고 권한이 있는 사람들이 볼 수 있게 한다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS Session Manager]]></title>
            <link>https://velog.io/@ziggy_stardust/AWS-Session-Manager</link>
            <guid>https://velog.io/@ziggy_stardust/AWS-Session-Manager</guid>
            <pubDate>Sat, 11 Jan 2025 17:45:01 GMT</pubDate>
            <description><![CDATA[<p>Session Manager 는 다음과 같은 문제를 해결합니다..</p>
<p>이전에 ec2 에 접근하려면 pem 같은 키 파일을 이용해 ssh 로 접근해야했습니다.
그리고 여러 명에서 개발하는 경우 이런 키 파일을 공유해줘야하는데 그렇게 여러 장소에서 관리하게 될 경우 키를 유출하는 문제도 발생할 수 있습니다.</p>
<p>Session Manager 를 사용하면
IAM 유저 단위로 제어하기에 키 파일을 따로 쓸 필요가 없어 위와 같은 문제를 해결해줍니다.
웹브라우저 기반이라 OS 에 종속되지 않습니다.</p>
<p>그리고 누가 접근했는지에 대한 로깅도 CloudTrail 을 이용해 쉽게 할 수 있습니다. 
접속 기록과 명령어 로그들을 S3, CloudWatch 로 전송도 가능합니다.
그리고 EventBrdige 같은 AWS 서비스와 연동해 실시간 접근 알림과 같은 시나리오도 기대할 수 있습니다.</p>
<p><a href="https://www.youtube.com/watch?v=2Xb2JXV5Llo&amp;t=326s">https://www.youtube.com/watch?v=2Xb2JXV5Llo&amp;t=326s</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[고가용성과 장애내구성]]></title>
            <link>https://velog.io/@ziggy_stardust/%EA%B3%A0%EA%B0%80%EC%9A%A9%EC%84%B1%EA%B3%BC-%EC%9E%A5%EC%95%A0%EB%82%B4%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@ziggy_stardust/%EA%B3%A0%EA%B0%80%EC%9A%A9%EC%84%B1%EA%B3%BC-%EC%9E%A5%EC%95%A0%EB%82%B4%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Sat, 11 Jan 2025 17:20:28 GMT</pubDate>
            <description><![CDATA[<p><code>고가용성</code> : 장애 상황을 해결하고 정상적으로 서비스를 제공하는 능력
<code>장애내구성</code> : 장애 상황에도 정상적으로 서비스를 제공하는 능력</p>
<p><code>재해복구성</code> : 장애 상황을 해결하려는 능력</p>
<p><code>고가용성</code> 은 <code>장애내구성</code> 과 <code>재해복구성</code>  을 합친 개념으로볼 수 있다.</p>
<p><code>고가용성</code> , <code>장애내구성</code>  을 갖추지 못한 서버는 장애 상황 시 그냥 터져버린다.</p>
<p><code>고가용성</code> 을 갖췄다면 장애 발생 시 다른 유효한 서버로 라우팅 해준다. 그 전환 과정 동안은 에러 발생 (로드밸런스)</p>
<hr>
<p><code>확장성</code> : 쉽고 빠르게 규모를 늘릴 수 있는 능력</p>
<ul>
<li>수요에 맞게 자원을 관리하기 위해</li>
</ul>
<p><code>탄력성</code> : 쉽고 빠르게 규모를 늘리거나 줄일 수 있는 능력</p>
<ul>
<li>불필요한 자원을 쉽게 관리하고 비용을 최적화하기 위해 사용</li>
<li>확장성의 큰 개념</li>
</ul>
<p><a href="https://www.youtube.com/watch?v=c7WjVkaUSj4">https://www.youtube.com/watch?v=c7WjVkaUSj4</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠버네티스 정리]]></title>
            <link>https://velog.io/@ziggy_stardust/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@ziggy_stardust/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 10 Jan 2025 18:17:04 GMT</pubDate>
            <description><![CDATA[<ul>
<li>파드
쿠베의 가장 작은 배포 단위
하나 이상의 컨테이너 가짐, 같은 네트워크 네임스페이스(아이피, 포트)를 가진다. 
동일한 저장소 (볼륨) 접근 가능하다.</li>
</ul>
<p><strong>특징</strong>
IP 가 고정되지 않고 한 번 쓰면 버림
파드가 새로 생성되면 아이피가 달라질 수 있다.
같은 네트워크 네임스페이스와 볼륨을 가집니다.
실제론 레플리카셋, 디플로이먼트에서 관리되지만 최소 배포 단위다.</p>
<p>파드 내부에선 브릿지 통신 (컨테이너들이)</p>
<p>파드는 같은 노드 안의 다른 파드에 접근 가능
서비스 인그레스를 통해 커베 클러스터 외로도 갈 수 있다.</p>
<hr>
<ul>
<li>레플리카셋
파드를 지정한 수만큼 유지시키는 오브젝트
라벨 셀럭트를 통해서 파드들을 관리하고
오토스케일링이 가능하며
디플로이먼트 바로 아래에 있는 오브젝트다 포함되는 느낌.</li>
</ul>
<hr>
<ul>
<li>디벨롭먼트
애플리케이션 배포와 업데이트 등 관리를 담당하는 오브젝트
선언적으로 애플리케이션을 구성할 수 있고 롤링 배포가 가능하며 롤백이 쉽습니다. 레플리카셋이 가지고 있던 스케일링, 파드 레플리카 수 유지가 가능</li>
</ul>
<hr>
<ul>
<li>서비스
외부 네트워크 트래픽을 파드로 제공하는 오브젝트</li>
</ul>
<p>클러스터아이피 : 클러스터 내부에서 다루는 아이피 (사설 아이피 냄새)
노드포트 : 외부와 연결될 노드포트 (30000번대 를 사용)
externalName : 외부 DNS 레코드와 내부 DNS 레코드를 매핑한다.
로드밸런스 : 로드밸런스는?! 로드밸런스다. ...
Selector : 라벨을 기준으로 트래픽 보낼 파드들을 구분
port/target port : 서비스로 들어오는 포트와 파드의 내부 포트를 매핑
DNS 통합 : 클러스터 내부에서 사용하는 DNS 로 서비스 주소를 해석 가능</p>
<p>인그레스를 통해 외부 트래픽을 받고 이게 서비스에 들어간다. 이 서비스는 클러스터 아이피를 가지며 변경되지 않아 고정 아이피처럼 사용할 수 있다 그리고 로드밸런싱을 통해 파드들에게 전달하고 파드들은 라벨 셀렉터를 통해서 할 수 있다. 서비스는 DNS 로 해석도 가능하다. 복잡한 구조에서 변하지 않는 것은 정말 소중한것 같다.</p>
<hr>
<ul>
<li>CNI (container network interface)
컨테이너 환경에서 네트워크를 쉽게 구성하고 관리하기 위한 표준 인터페이스</li>
</ul>
<p>새로운 파드로 이어질 수 있는 환경을 만들어준다.</p>
<p>워커노드 kubelet 가 파드를 생성하란 명령어 받는다.
도로를 만들어야하는데..</p>
<p>우선 cni 플러그인에 진입하면 가상 이더넷을 만들고 브릿지가 생성되고 아이피테이블을 생성하고 파드와 연결할 수 있는 환경을 구축한다.
(요구사항에 맞게 네트워크 인터페이스를 생성)</p>
<ul>
<li>kubernates API Server : 사용자의 요청을 받아 각 노드 kubelet 에게 전달</li>
<li>kubelet (Node) : 노드 내 파드를 생성하고 CNI 에게 네트워크 인터페이스를 위임</li>
<li>CNI : 네트워크 인터페이스 구성 (가상 이더넷, 브릿지, 아이피설정)</li>
<li>파드 : CNI 가 설정한 네트워크로 통신한다.</li>
<li>가상이더넷 : 이더넷은 랜카드?! 인터넷 선 꼽는.. 그걸 소프트웨어적으로 만든 것. 컨ㅌ이너와 호스트를 연결하며 양쪽 끝에 서로의 네트워크 공간을 연결한다..</li>
<li>브릿지 : 여러 네트워크 세그먼트를 전달하는 가상의 L2 스위치 , 컨테이너간 통신을 중계한다..</li>
<li>iptables : 리눅스의 패킷 필터링 도구 컨테이너 트래픽의 라우팅, NAT, 보안규칙</li>
</ul>
<hr>
<p>Calico
으악! 진짜 넘! 어렵다! 으악!
클라우드 네이티브 환경에서 네트워크 설정과 네트워크 보안을 제공하는 오픈소스 프로젝트.
L3 라우팅을 방식을 사용하며 IP 계층에서 파드간 통신을 단순히 하며 확장성 있게 한다.
eBPF 모드 지원 (높은 성능 , 유연성을 제공하며 다양한 기능 제공.. 어우 어려워)
Felix 는 에이전트로 라우팅, 네트워크 보안 설정(iptables, ) 을 담당한다.
Typha 는 대규모 클러스터에서 etcd 로의 접근을 최소화하기 위한 캐시 레이어? (팬아웃 감소 효과)
BGP (Border Gateway Protocol) 노드 간 BGP 를 통해 라우팅 정보를 교환하여 L3 라우팅 방식으로 처리한다..
(네트워크 지식이 너무 부족하다... )</p>
<hr>
<ul>
<li>인그레스
외부 트래픽을 서비스에 라우팅되는 트래픽을 제어하는 리소스
클러스터 외부에서 클러스터에 접근할 땐 노드포트, 인그레스, 로드밸런스 타입이 있어야한다..
인그레스 컨트롤러가 라우팅 규칙을 확인하고 도악한다.</li>
</ul>
<p>L7 스위치를 지향해 패스와 도메인으로 라우팅이 가능하며 HTTPS, TLS 가 가능하다.</p>
<p>스위치와는 Nodeport, clusterIp, 로드밸런스 타입, 를 통해서 상호작용한다
DNS 는 도메인을 통해서 라우팅할 때 아이피 주소로 도메인 매핑이 필요할 때 사용한다.</p>
<hr>
<p>칼리코는 파드와 파드, 노드와 파ㅏ드 사이 통신을 위해 사용한다 그리고 L3, 4 수준에서 트래픽 차단이 가능하다 (NetworkPolicy)</p>
<p>인그레스 는 클러스터 외부 -&gt; 클러스터 내부를 위해 사용
L7 레벨에서 적절한 service로 라우팅한다.</p>
<hr>
<ul>
<li>job
일회성 작업을 위한 리소스. 지정된 작업을 완료할 때 까지 반복한다.
병렬로 실행 가능하고 특정 완료 횟수를 지정할 수 있다.</li>
</ul>
<hr>
<ul>
<li>CronJob
반복 스케줄 작업을 위한 리소스.
수행 시간이 너무 길어 다음 주기에 닿으면 또 작업이 실행되니 주의하자.
Job 축적이 가능하니 히스토리를 관리하자
타임존은 UCT 기준이라  한국 시간대로 바꿔야한다.
성공했다 떴는데 실패하는 경우 있을 수 있으니 성공했을 때 로그도 기록하자 (휴먼 에러에 가까운듯)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[K8s metaspace name 과 labels]]></title>
            <link>https://velog.io/@ziggy_stardust/K8s-metaspace-name-%EA%B3%BC-labels</link>
            <guid>https://velog.io/@ziggy_stardust/K8s-metaspace-name-%EA%B3%BC-labels</guid>
            <pubDate>Fri, 10 Jan 2025 09:18:29 GMT</pubDate>
            <description><![CDATA[<p>K8s 의 오브젝트는 namespace 에서 존재하며 <code>name</code> 으로 <strong>식별</strong>됩니다. 반면 <code>labes</code> 는 <strong>중복을 허용</strong>하며 <strong>검색, 그루핑</strong> 등에서 사용됩니다.</p>
<h3 id="metadataname">metadata.name</h3>
<ul>
<li>오브젝트의 식별자</li>
<li>한 namespace 안에서 중복될 수 없습니다.</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: popod
spec:
  containers:
    - name: nginx
      image: nginx:latest</code></pre>
<p><strong>특정 오브젝트 조회</strong></p>
<pre><code>kubectl get Pod popod</code></pre><blockquote>
<p>굳이 오브젝트 타입을 적어줘야할까요?
오브젝트 타입으로 조회하는 명령어에 확장된 기능이라 그런건가 잘 모르겠습니다.
-n, --namespace 명령어로 조회 namespace 를 지정할 수 있습니다.
기본값은 <code>default</code> namespace 입니다.</p>
</blockquote>
<h3 id="metadatalabels">metadata.labels</h3>
<ul>
<li><strong>중복이 가능하며 복수의 label 을 가질 수 있습니다.</strong></li>
<li><strong>검색, 그루핑, 필터링</strong> 등에 사용됩니다.<pre><code class="language-yml">apiVersion: v1
kind: Pod
metadata:
name: popod
labels:
  app: web-server
  env: production
spec:
containers:
  - name: nginx
    image: nginx:latest</code></pre>
</li>
</ul>
<p><strong>label selector 를 응용한 조회</strong></p>
<pre><code>kubectl get pods -l app=web-server</code></pre><h3 id="실용적인-포인트">실용적인 포인트</h3>
<ol>
<li><strong>label 도 되도록 작성합시다.</strong>
label 의 검색, 그루핑 기능이 참 유용하니 되도록 작성합시다.</li>
<li><strong>label 필터링에 참 좋습니다.</strong>
배포 환경, 서비스 이름, 팀 이름 을 label 로 지정하면 리소스를 관리하기 좋습니다.</li>
<li><strong>scale out 같은 상황에 좋은 label</strong>
scale out 이 일어나면 Pod 의 name 은 바뀔 수 있지만 label 로 묶어 다룰 수 있습니다.이 덕분에 service 오브젝트를 통해 언제나 트래픽을 받을 수 있습니다.</li>
<li><strong>label 을 통해 일괄적 리소스 제거</strong>
label 을 통해 일괄적으로 처리하기 참 좋으니 가능한 작성합시다.<pre><code>kubectl delete pod -l app=web-server</code></pre></li>
</ol>
<br>

<h3 id="정리">정리</h3>
<ol>
<li><code>name</code> <ul>
<li>namespace 내에서 유일하며 오브젝트를 식별하는 값</li>
</ul>
</li>
<li><code>labels</code><ul>
<li>여러 리소스에서 중복되어도 되며 한 개 이상을 가져도된다.</li>
<li>key-value 형태입니다.</li>
<li>검색, 필터링, 그루핑 시 좋으니 꼭 사용합시다.</li>
</ul>
</li>
</ol>
]]></description>
        </item>
    </channel>
</rss>