<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>junior_datalist</title>
    <link>https://junior-datalist.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 7 May 2026 08:49:50 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Hugehoo</managingEditor>
    <image>
      <title>junior_datalist</title>
      <url>https://tistory1.daumcdn.net/tistory/3513367/attach/131c9fb785764a27a06930fe4b3d0fa9</url>
      <link>https://junior-datalist.tistory.com</link>
    </image>
    <item>
      <title>블로그 이사합니다!</title>
      <link>https://junior-datalist.tistory.com/404</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 기반의 개인 블로그를 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hugehoo-blog.vercel.app/blog&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hugehoo-blog.vercel.app/blog&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 위 블로그에 글을 더 자주 올릴 예정입니다 :)&amp;nbsp;&lt;/p&gt;</description>
      <author>Hugehoo</author>
      <guid isPermaLink="true">https://junior-datalist.tistory.com/404</guid>
      <comments>https://junior-datalist.tistory.com/404#entry404comment</comments>
      <pubDate>Sun, 16 Mar 2025 11:58:11 +0900</pubDate>
    </item>
    <item>
      <title>[Go] Ellipsis 의 활용과 주의사항</title>
      <link>https://junior-datalist.tistory.com/403</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;Go 에서 &lt;b&gt;&lt;code&gt;...&lt;/code&gt;&lt;/b&gt; 은 Ellipsis (줄임표) 연산자라고 부른다. JS 에서도 이런 문법이 있던 걸로 기억한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;Ellipsis 연산자의 주요 용도는 두가지 인데 &lt;code&gt;1) 가변인자 매개변수&lt;/code&gt;, &lt;code&gt;2) 슬라이스 확장&lt;/code&gt; 정도로 정의할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000; background-color: #f6e199;&quot;&gt;&lt;b&gt;1. 가변인자 (Variadic Parameters)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;가변 인자는 함수가 임의의 개수의 인자를 받을 수 있게 하는 기능이다. 예를 들어 int 형 인자를 파라미터로 넘겨야 할 때 한개를 넘기거나, 그 이상의 수를 넘겨야 하는 경우를 생각해보자. 인자의 수가 변하는 경우, 보통 배열을 인자로 받아야 한다고 생각할 수 있지만 ellipsis 를 사용하면 파라미터의 타입을 굳이 배열로 변경할 필요가 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;아래 예시를 살펴보자. sum() 함수는 인자로 int 형 ellipsis 를 받을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737473427887&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 함수 정의 시
func sum(nums ...int) int {
    total := 0
    for _, num := range nums {
        total += num
    }
    return total
}

// 함수 호출
sum(1, 2, 3)
sum(1, 2, 3, 4, 5)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;위 예시를 보고 띠용 할 수 있다. 그냥 배열을 넘기면 되는 것 아닌가? 굳이 ellipsis 를 인자에 선언해야하나? 그런 생각이 들었다면 아래 코드를 보자. 차이가 느껴지실랑가 모르겠다. 가변 인자를 사용한 sum() 함수는 굳이 배열을 사용하지 않고 직접 함수 호출이 가능하지만, 배열 슬라이스를 인자로 받는 sumArray() 는 슬라이스를 명시적으로 생성해줘야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737474139073&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 가변 인자 사용
func sum(numbers ...int) int {
    ...
}

// 직접 호출이 간편하다
sum(1, 2, 3)

// 배열/슬라이스 사용
func sumArray(numbers []int) int {
    ...
}

// 호출 시 슬라이스를 명시적으로 생성해야 한다
sumArray([]int{1, 2, 3})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;또 다른 예시를 살펴보자. 다음 예시는 문자를 concatenate 하는 함수로, elemetns 인자는 가변적으로 string 타입의 변수를 넣을 수 있다. concatenate 하고 싶은 string 이 몇개이든 상관없이 인자를 추가할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737474489575&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 문자열 결합 함수
func concatenate(delimeter string, elements ...string) string {
    return strings.Join(elements, delimeter)
}

result := concatenate(&quot;-&quot;, &quot;Go&quot;, &quot;is&quot;, &quot;awesome&quot;)
fmt.Println(result) // 출력: Go-is-awesome

concatenate(&quot;-&quot;, &quot;Go&quot;, &quot;is&quot;, &quot;awesome&quot;, &quot;쭉쭉&quot;, &quot;추가&quot;, &quot;가능&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000; background-color: #f6e199;&quot;&gt;&lt;b&gt;2. 슬라이스 확장&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;다음은 슬라이스 확장 시에도 ellipsis 를 유용하게 활용할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이전 1번 예시에서는 type 앞에 &lt;code&gt;...&lt;/code&gt; 이 붙었지만, 슬라이스를 확장하는(쉽게 말해 배열을 벗겨버리는) 경우엔 &lt;code&gt;...&lt;/code&gt;&amp;nbsp;를 변수 뒤에 붙이면 끝이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737473458060&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;nums := []int{1, 2, 3}
otherNums := []int{4, 5, 6}

// 슬라이스를 개별 요소로 확장하여 전달
allNums := append(nums, otherNums...)


// ellipsis를 사용하지 않는다면 아래처럼 각 인자를 순회해야 한다.
allNums := nums
for _, num := range otherNums {
    allNums = append(allNums, num)
}
// ellipsis 를 사용한 것 보단 가독성이 좋지않다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000; background-color: #f6e199;&quot;&gt;&lt;b&gt;3. 실제 예시&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이번 글을 쓰게 된 이유를 코드로 설명해보려 한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;기존에는 하나의 redis 엔드포인트를 사용하던 시스템에서, replica 엔드포인트를 추가하게 되면서 총 2개의 endpoint 를 쓰게 됐다. Redis 설정값을 초기화 할 때 이 endpoint 값을 주입 시 ellipsis 를 사용할 수 있었다. 기존 코드에서는 하나의 endpoint 만 받지만, 개선된 코드에서는 string 을 ellipsis 로 받아 2개 이상의 endpoint 도 인자로 받을 수 있게 됐다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737475742949&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기존
func mustConnectRedis(endpoint string) redis.UniversalClient {
	...
    redisClient := redis.NewUniversalClient(&amp;amp;redis.UniversalOptions{
		Addrs:        endpoint,
		ReadOnly:     true,
	})
}
mustConnectRedis(&quot;127.0.0.1:6379&quot;)



// 슬라이스 확장하도록 개선
func mustConnectRedis(endpoint ...string) redis.UniversalClient {
	...
    redisClient := redis.NewUniversalClient(&amp;amp;redis.UniversalOptions{
		Addrs:        endpoint,
		ReadOnly:     true,
	})
}
mustConnectRedis(&quot;127.0.0.1:6379&quot;, &quot;127.0.0.1:6380&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000; background-color: #f6e199;&quot;&gt;&lt;b&gt;주의사항과 팁&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 가변인자는 항상 함수 마지막 매개변수여야 한다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737475220002&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 올바른 사용
func correct(prefix string, numbers ...int)

// 잘못된 사용
func incorrect(numbers ...int, prefix string) // 컴파일 에러&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. 타입 안정성 유지&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737475242715&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func betterFunction(numbers ...int) // interface{} 대신 구체적인 타입 사용 권장
func avoidThis(args ...interface{}) // 가능하면 피할것&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. 작은 데이터 셋에서 사용 권장&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;가독성 측면에서 큰 장점을 보이는 ellipsis 지만 성능 측면도 고려해야 한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;ellipsis 를 사용하면 내부적으로 슬라이스가 생성되므로 매우 큰 데이터 셋을 다룰 때는 직접 슬라이스를 전달하는 것이 더 효율적이기 때문이다. 아래 코드를 보며 자세히 알아보자.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737476734841&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func sum(numbers ...int) int {
    // numbers는 내부적으로 슬라이스로 변환됨
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

// 호출 시
sum(1, 2, 3)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;sum() 이 실행될 때 내부적으로 일어나는 일을 정리하면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;1. 새로운 슬라이스가 생성됨&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;2. 인자들 (1,2,3)이 슬라이스로 복사됨&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;3. 함수 내부에서 슬라이스를 사용함.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;위 예시처럼 적은 수의 인자가 슬라이스로 복사되어도 성능 차이는 거의 발생하지 않을 거라 생각한다. 하지만 만약 크기가 (오버해서) 100만인 슬라이스를 초기화하여 ellipsis 로 넘긴다고 가정해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;케이스 1 처럼 슬라이스를 확장하면 sumVariadic() 내부에서 크기가 100만인 슬라이스가 새로 생성되어 복사되며 이러한 작업이 반복되면 심각한 메모리 문제를 초래할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;때문에 데이터 셋이 너무 커질 것 같을 땐 &lt;span style=&quot;text-align: start;&quot;&gt;ellipsis 를 사용하기 보다 케이스 2 처럼 기존 슬라이스를 직접 사용하는 것이 메모리 측면에서 효율적이라 생각한다.&lt;/span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737476996078&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;numbers := make([]int, 1000000)

// 케이스 1: 추가적인 슬라이스 생성 및 복사 발생
sumVariadic(numbers...)  

// 케이스 2: 기존 슬라이스 직접 사용 (추가 메모리 할당 없음)
sumSlice(numbers)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic'; background-color: #f6e199;&quot;&gt;&lt;b&gt;정리하면&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;ellipsis 를 사용하는 이유는 코드 유연성과 재사용성을 향상시킬 수 있다. 함수 호출 시 더 깔끔하고 직관적인 문법을 사용하여 가독성을 높일 수 있고, 매개변수 개수의 유연성도 높이는 장점을 노릴 수 있다. 단 슬라이스를 확장하면 함수 내부에서 해당 슬라이스 크기만큼 새로운 슬라이스를 생성하고 복사하는 작업이 발생할 수 있다. 때문에 크기가 큰 데이터를 사용하는 경우라면 메모리 측면도 고려하여 ellipsis 를 적절히 사용하는 것을 권장한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Backend/  Go</category>
      <author>Hugehoo</author>
      <guid isPermaLink="true">https://junior-datalist.tistory.com/403</guid>
      <comments>https://junior-datalist.tistory.com/403#entry403comment</comments>
      <pubDate>Wed, 22 Jan 2025 01:35:10 +0900</pubDate>
    </item>
    <item>
      <title>카나리 배포 : 비율 설정하면서 배운 것</title>
      <link>https://junior-datalist.tistory.com/402</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;[250113]&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;오늘 상용 배포 때 알게 된 카나리 배포 관련 내용을 간략히 기록&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;카나리 배포&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;카나리 배포의 &lt;b&gt;카나리(Canary)&lt;/b&gt; 는 탄광에서 나오는 유독 가스를 사전에 감지하기 위해 광부들이 가스에 민감한 카나리 새를 데리고 갱도를 사전 탐사(?) 하는데서 비롯되어, 다가올 위험을 사전에 알려준다는 의미로 사용된다. 카나리 배포는 새로운 버전의 애플리케이션을 모든 사용자에게 한 번에 릴리즈하지 않고 일부 사용자에게만 먼저 제공하여 점진적으로 롤아웃하는 배포 방식이다. 일부만 릴리즈 된 서버에서 에러가 발생하지 않거나 정상적으로 동작하는 것을 확인한 후에, 개발자는 점진적으로 모든 시스템에 새로운 버전의 애플리케이션을 배포할 수 있다. 좀 더 정리된 말로 &lt;b&gt;카나리 배포는 일부 서버에만 새로운 버전을 배포하고, 일부 트래픽을 새 버전으로 분산하는 방법을 의미한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;상황 가정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;쿠버네티스 환경에서 여덟개의 파드로 운영중인 시스템이 있다고 가정하자. 이 시스템을 카나리 배포한다면 어떻게 진행해야할까? 우선 필자는 카나리 배포가 점진적으로 배포한다는 개념만 알았지, 어떻게 점진적으로 배포하는 것인지 구체적으로 생각해보지 못했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;카나리 배포는 점진적으로 서비스를 배포하기에 전체 파드 중 몇개의 파드에 신규 버전을 릴리즈할지 개발자나 운영자가 정할 수 있다. 만약 카나리 배포 시 weight 를 &lt;b&gt;10%&lt;/b&gt; 로 정하면 &lt;b&gt;8개 중 1개의 파드(=0.8개의 반올림)&lt;/b&gt; 에만 신규 버전을 릴리즈 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;651&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1Ulgu/btsLL4I9F0S/YZNBo8piboXEE5jsLZHZVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1Ulgu/btsLL4I9F0S/YZNBo8piboXEE5jsLZHZVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1Ulgu/btsLL4I9F0S/YZNBo8piboXEE5jsLZHZVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1Ulgu%2FbtsLL4I9F0S%2FYZNBo8piboXEE5jsLZHZVk%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;632&quot; height=&quot;567&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;651&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;651&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGXpfO/btsLLPFuSvd/ppw6NC9kroycgEkgynFx9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGXpfO/btsLLPFuSvd/ppw6NC9kroycgEkgynFx9K/img.png&quot; data-alt=&quot;화질구지지만,, CanaryReplicaCount 와 CanaryWeight 만 확인하자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGXpfO/btsLLPFuSvd/ppw6NC9kroycgEkgynFx9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGXpfO%2FbtsLLPFuSvd%2Fppw6NC9kroycgEkgynFx9K%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;726&quot; height=&quot;651&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;651&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;화질구지지만,, CanaryReplicaCount 와 CanaryWeight 만 확인하자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;사내 배포 툴에서는 위처럼 CanaryWeight 와 CanaryReplicaCount 를 지정할 수 있다. 이 두 값의 적절한 설정이 중요한데, 만약 weight 는 50% 로 설정하고 replica count 는&amp;nbsp; 1로 설정하면 전체 트래픽의 50%가 단 하나의 파드로 집중되어 서버 과부하가 발생할 수 있다. 그렇기에 replica count 와 weight 비율을 상황에 맞게 조절하며 배포할 수 있어야 한다. 개인적으로 코드 업데이트가 크지 않을 땐 weight 값을 높여도 되지만, 대규모 마이그레이션이나 리팩토링된 코드를 배포하는 경우라면 weight 를 소극적으로 선정하여 트래픽 추이를 봐야한다고 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;517&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUpuYt/btsLK1fjANL/kyZnwpkVTTOQpdxRJB2561/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUpuYt/btsLK1fjANL/kyZnwpkVTTOQpdxRJB2561/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUpuYt/btsLK1fjANL/kyZnwpkVTTOQpdxRJB2561/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUpuYt%2FbtsLK1fjANL%2FkyZnwpkVTTOQpdxRJB2561%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;818&quot; height=&quot;483&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;517&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위 이미지는 weight 를 10 으로 맞추고 배포했을 때 서버 metric 을 캡쳐한 것 이다. 빨간색 동그라미에선 아주 적은 비율의 트래픽만이 신규 버전으로 할당된 것을 볼 수 있다. 메트릭을 확인하면서 신규 버전이 안정적으로 배포됐다고 판단되면 완전히 신규 버전 서버를 배포할 수 있다 (by 개발자). 만약 일부 배포된 신규 버전이 불안정하거나 error rate 가 점점 높아진다면 신규버전을 rollback 하고 모든 파드를 기존 애플리케이션으로 유지하도록 조치하면 된다. 위 이미지에서는 신규 버전(보라색)이 안정적으로 릴리즈 됐기에 구버전의 서버(파란색)를 점진적으로 제거한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;+ 현재(회사)는 Spinnaker 의 웹 인터페이스를 통해 canary 배포 진행상황을 모니터링하고 제어할 수 있다. 추후 ArgoCd 로 마이그레이션이 예정되어 있는데 두 도구의 카나리 배포 방식의 차이점도 추가로 조사해봐야겠다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Backend</category>
      <author>Hugehoo</author>
      <guid isPermaLink="true">https://junior-datalist.tistory.com/402</guid>
      <comments>https://junior-datalist.tistory.com/402#entry402comment</comments>
      <pubDate>Mon, 13 Jan 2025 22:39:36 +0900</pubDate>
    </item>
    <item>
      <title>[Go] Pointer 쉽게 이해해보자</title>
      <link>https://junior-datalist.tistory.com/401</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목차&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Pointer 기본 개념 잡기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Pointer 를 사용하는 이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span data-token-index=&quot;0&quot;&gt;Go 는 왜 굳이 포인터를 도입했을까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Pointer 기본 개념 잡기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;그래서 포인터가 무엇일까? 포인터란 메모리 주소를 저장하는 특별한 변수를 의미한다. &lt;b&gt;메모리 주소??&lt;/b&gt; 직관적으로 이해되지 않는 문자와 숫자 값을 떠올렸다면 정답이다. 메모리 주소는 컴퓨터의 RAM(Random Access Memory)에서 특정 데이터가 저장된 고유한 위치를 나타내는 숫자값을 의미한다. 개발자가 어떤 변수를 선언하여 값을 할당하면, 변수 데이터는 메모리의 특정 위치에 할당된 값을 저장된다. 즉 해당 변수는 특정 메모리 위치에 할당받은 값을 저장한다. 이 메모리 위치, 즉 메모리 주소를 나타내는 것이 바로 포인터다! 코드로 이해해보자.&lt;/span&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735739268649&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
- 포인터 선언
- var 변수명 *타입 &amp;lt;&amp;lt;&amp;lt; asterik 을 붙여 포인터 선언
- int 타입의 포인터
*/
var po *int&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;po 변수는 포인터로 선언되었다. 근데 이제 int 를 곁들인...&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;메모리 특정 위치에 int 타입의 값이 저장되면 po 로 해당 int 타입 변수가 어떤 메모리 주소를 참조하는지 알 수 있다. 아래 예시에서 number 라는 Int type 변수가 나온다. 이 변수가 어떤 값, 예를 들어 100을 저장한다고 하면 메모리의 어떤 주소에 이 100이란 값이 저장될 것이다. 우리 개발자들은 100이 어떤 메모리 주소에 저장되는지 신경쓰지 않아도 되지만, 컴파일러 입장에서는 이 메모리 주소를 알아야 한다. 이 때 po 라는 포인터 변수를 통해 number 변수가 어느 메모리 주소를 참조하는지 알아낼 수 있는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735739767024&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var number int
var po *int
po = &amp;amp;number&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;이 때 number 변수 앞에 붙은 ampersand(&amp;amp;) 는 무엇을 의미하는가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;&amp;amp; 기호는 메모리의 주소를 명시하라는 operator 로 number 변수가 어떤 메모리 주소에 값을 저장하는지 알 수 있다. 즉 &amp;amp;number 는 메모리 주소를 의미하기에 po 포인터 변수에 할당할 수 있는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735740113897&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func main() {
	var number int = 10
	var pointer *int
	pointer = &amp;amp;number
	
	fmt.Println(&quot;number:&quot;, number)
	fmt.Println(&quot;pointer:&quot;, pointer)
	fmt.Println(&quot;*pointer:&quot;, *pointer)
	fmt.Println(&quot;&amp;amp;number:&quot;, &amp;amp;number)
}

&amp;gt; number: 10
&amp;gt; pointer: 0xc000104040
&amp;gt; *pointer: 10
&amp;gt; &amp;amp;number: 0xc000104040&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;number 변수의 10 이 저장된 위치가 `0xc000104040` 이므로, number 의 메모리 주소를 할당받은 pointer 역시 동일한 메모리 주소값을 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;역참조&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서 pointer 변수에 *를 붙이면 10이 출력되는 것을 확인했다. pointer 변수는 number 의 메모리 주소를 참조하는 포인터인데, 이 포인터에 asterik(*) 을 붙이면 number 값에 접근할 수 있다. 예제의 세번째 출력문에서 포인터 변수에 asterik 을 붙여 값(10)에 접근할 수 있는걸 알 수 있다. 있음을 보여준다. 값에 접근할 수 있다는 건, 수정도 가능하다는 의미다. 아래 예제를 통해 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1735740532995&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func main() {
	var number int
	var pointer *int
	number = 10
	pointer = &amp;amp;number
	fmt.Println(&quot;[before] number:&quot;, number)
	// 역참조로 값 수정 시도
	*pointer = 0
	fmt.Println(&quot;*pointer:&quot;, *pointer)
	fmt.Println(&quot;[after] number:&quot;, number)
}

&amp;gt; [before] number: 10
&amp;gt; *pointer: 0
&amp;gt; [after] number: 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 최초 number 변수는 10 을 할당받지만 마지막 출력문에서는 0 을 가지고 있다. *pointer 를 통해 number 변수의 메모리 주소 값(value of memory address) 에 접근하여 다른 값(0)을 할당할 수 있다.&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;Pointer 를 사용하는 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 구조체에서 pointer 를 사용해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개 메서드 (editName, editNamePointer) 의 메서드 로직은 같지만 메서드 시그니처에 차이가 있다. 전자는 Item 구조체의 값을 받고 후자는 Item 구조체의 포인터를 전달받는다. 이것이 어떤 차이를 만드는지 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1735741359437&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Item struct { 
	name string
}

func editName(item Item) {
	item.name = &quot;커피&quot;
	fmt.Println(&quot;editName:&quot;, item.name)
	fmt.Println(&amp;amp;item.name)
}

func editNamePointer(item *Item) {
	item.name = &quot;커피&quot;
	fmt.Println(&quot;editName:&quot;, item.name)
	fmt.Println(&amp;amp;item.name)
}

func main() {
	item := Item {&quot;음료수&quot;}
	fmt.Println(&quot;출력:&quot;, item.name)
	fmt.Println(&quot;출력:&quot;, &amp;amp;item.name)
	
	editName(item)
	fmt.Println(&quot;결과:&quot;, item.name)
	
	// &amp;amp;item 은 item 구조체의 메모리 주소 -&amp;gt; 인자로 메모리 주소를 넘긴다.
	editNamePointer(&amp;amp;item) // item 을 넣으면 compile Error 발생
	fmt.Println(&quot;결과:&quot;, item.name)
}

&amp;gt; 출력: 음료수
&amp;gt; 출력: 0xc00009a070
&amp;gt; editName: 커피
&amp;gt; 0xc00009a090
&amp;gt; 결과: 음료수 // name 이 커피로 변경되지 않음 (원본 영향 받지 않음)
&amp;gt; editName: 커피
&amp;gt; 0xc00009a070
&amp;gt; 결과: 커피 // name 이 커피로 변경됨 (원본 수정됨)

// 각 함수 내부에서 메모리 주소를 출력하면 서로 다른 메모리 주소가 넘어왔음을 확인할 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go 는 기본적으로 Call by value 를 따르기에 매개변수를 복사해서 함수 내부로 전달한다. 즉 원본 객체가 아닌 복사본을 만들어 메서드의 매개변수로 넘기는 셈이다. 만약 원본을 수정하고 싶다면 포인터를 통해 수정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;editNamePointer 는 Item 객체의 &lt;b&gt;메모리 주소&lt;/b&gt;를 &lt;b&gt;포인터&lt;/b&gt; &lt;b&gt;변수&lt;/b&gt; 형태로 전달받는다. 즉 editNamePointer() 내부는 전달받은 인자의 메모리 주소로 접근 가능하여 원본의 값을 수정할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 editName 는 인자로 &lt;b&gt;일반 객체&lt;/b&gt;를 전달받는데, 여기서 일반 객체란 원본의 복사본을 의미한다. 즉 복사본의 값을 수정하더라도 원본은 영향을 받지 않기 때문에 editName() 함수를 escape 한 후에 item 객체의 값을 확인하면 변하지 않은 걸 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Q.&lt;/b&gt; &lt;/span&gt;editNamePointer 는 인자로 포인터 변수를 받는다. 인자는 asterik(*)인데 함수 호출부에서 넘기는 것은 ampersand(&amp;amp;) 다. 그럼 메서드 정의 할 때 &amp;amp; 타입으로 인자를 정의하면 왜 컴파일 에러가 발생할까?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUR1GY/btsLBzpbFDK/d0JcSQKwlQzkSwZaHI66zK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUR1GY/btsLBzpbFDK/d0JcSQKwlQzkSwZaHI66zK/img.png&quot; data-alt=&quot;바로 터지는 컴파일 에러&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUR1GY/btsLBzpbFDK/d0JcSQKwlQzkSwZaHI66zK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUR1GY%2FbtsLBzpbFDK%2Fd0JcSQKwlQzkSwZaHI66zK%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;472&quot; height=&quot;265&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;265&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;amp; 는 연산자로써 &amp;ldquo;이 값의 메모리 주소를 가져온다&amp;rdquo; 를 의미한다. 즉 어떤 값의 메모리 주소를 가져올 때 사용하는 연산자이기에 타입을 선언시 사용하는 것은 적절하지 않다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;정리하면&lt;/b&gt;&lt;br /&gt;&lt;b&gt;- *Item 은 &amp;ldquo;Item 변수를 가리키는 포인터 타입&amp;rdquo;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;- &amp;amp;Item 은 &amp;ldquo;Item 변수의 메모리 주소&amp;rdquo;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;그럼 Go 는 왜 굳이 포인터를 도입했을까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Go 는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;안정성과 간결함이라는 언어 철학 아래 &lt;b&gt;call by value&lt;/b&gt; 로 설계되어 개발자가 코드의 동작을 명확히 이해하고, 메모리 관리와 관련된 버그를 줄이며, 직관적인 프로그래밍 경험을 제공한다. Go 가 call by reference 로 동작했다면, 데이터가 어디서 어떻게 변경되는지 추적하기 어려워 코드의 복잡성을 증가시킨다. Go의 &lt;b&gt;call by value&lt;/b&gt;는 이런 복잡성을 줄이고 코드의 예측 가능성을 높이는 효과를 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 언어 철학을 기조로 Pointer 를 생각해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1736003008860&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type HugeStruct struct {
    data [1000000]int
    // 매우 큰 데이터를 가진 구조체
}

// 포인터 사용하지 않을 경우
func processData(h HugeStruct) {
    // 1,000,000개의 정수가 모두 복사됨!
}

// 포인터 사용할 경우
func processDataPtr(h *HugeStruct) {
    // 메모리 주소만 복사 (8바이트 정도)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인터는 값 자체를 복사하지 않고 메모리 주소를 전달하여 메모리 사용량을 줄이고, 대규모 데이터를 다룰 때 불필요한 복사를 방지할 수 있다. 예시처럼 HugeStruct 같은 대규모 데이터를 값으로 전달하면 함수를 호출할 때마다 HugeStruct 전체가 메모리에 복사된다. 이 과정이 반복되면 최악의 경우 OOM(Out Of Memory)이 발생할 가능성도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 포인터를 사용한다면?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 데이터를 직접 복사하지 않고 메모리 주소만 전달하므로 복사에 따른 오버헤드가 줄어들고, 메모리 사용량도 크게 절감할 수 있다. 함수 호출 시 값 자체가 아닌 주소를 전달하기 때문에 동일한 데이터의 여러 복사본을 생성하지 않아도 된다. *HugeStruct와 같은 포인터를 함수로 전달하면, 함수 내부에서 데이터에 접근하거나 수정할 때도 메모리 주소를 통해 원본 데이터에 직접 접근할 수 있다. 함수 내부에서 데이터 수정이 필요한 경우엔 복사본이 아닌 원본 데이터를 직접 수정할 수 있어 불필요한 데이터 복사도 줄여 성능을 높일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 pointer 를 사용하면&amp;nbsp;&lt;b&gt;코드의 의도를 파악할 수 있다는 점&lt;/b&gt;을 보여준다.&lt;/p&gt;
&lt;pre id=&quot;code_1736004079963&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type User struct {
    name string
    age  int
}

// 포인터를 사용하면 함수가 원본 데이터를 변경할 수 있다는 것을 
// 타입 시그니처만으로도 명확하게 알 수 있음
func updateAge(u *User) {
    u.age += 1
}

// 포인터를 사용하지 않으면 값이 변경되지 않음을 암시
func displayInfo(u User) {
    fmt.Println(u.name, u.age)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 시그니처에 포인터를 정의하면 해당 함수 내부에서 인자로 전달받은 값을 수정하겠다는 의도를 드러낸다. 반면 포인터가 아닌 값 객체를 인자로 정의하면, 해당 함수 내부에서는 값을 수정하지 않고 사용(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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;번외) Java 의 포인터는?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 는 포인터 개념이 없는데 언어 자체가 이미 call by reference 로 설계 되어있기 때문이다. 자바의 경우 객체를 다룰 때 항상 참조(reference)를 통해 처리하는데, Go의 포인터와 달리 명시적이지 않고 언어 차원에서 &lt;b&gt;자동으로&lt;/b&gt; 처리된다.&lt;/p&gt;
&lt;pre id=&quot;code_1736005066460&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Java
class HugeObject {
    int[] data = new int[1000000];
    
    void processData() {
        // data 처리
    }
}

void someMethod(HugeObject obj) {  // 참조로 전달됨
    obj.processData();  // 원본 객체의 메서드 호출
}

// 사용
HugeObject huge = new HugeObject();
someMethod(huge);  // 참조만 전달됨 (실제 데이터는 복사되지 않음)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;go 와 달리 포인터를 명시하지 않더라도 언어 차원에서 자동으로 참조를 넘기기 때문에 별도의 메모리 복사가 발생되지 않는다. 즉 원본 데이터를 넘기는 것(처럼 보임)으로 메서드 내부에서 데이터를 직접 읽고 수정까지 할 수 있다. HugeObject 처럼 큰 객체를 전달할 때도 당연히 메모리 복사 문제가 발생하지 않는다.&lt;/p&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;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go가 포인터를 도입한 이유는 크게 세 가지로 정리할 수 있다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;메모리 효율성:&lt;/b&gt;&lt;b&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;큰 구조체나 데이터를 함수에 전달할 때 전체를 복사하지 않고 메모리 주소만 전달&lt;/li&gt;
&lt;li&gt;불필요한 메모리 사용을 줄이고 성능 향상 도모&lt;/li&gt;
&lt;li&gt;OOM(Out Of Memory) 같은 잠재적 문제 예방&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;코드 의도의 명확성:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&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;b&gt;개발자에게 더 많은 제어권 제공:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Call by value를 기본으로 하되, 필요한 경우 포인터를 통한 참조 전달 가능&lt;/li&gt;
&lt;li&gt;Java와 달리 값 복사와 참조 전달을 개발자가 명시적으로 선택 가능&lt;/li&gt;
&lt;li&gt;상황에 따른 최적의 방법 선택 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 Go의 포인터는 단순히 메모리 주소를 다루는 도구를 넘어, 성능 최적화와 코드 의도의 명확한 표현이라는 두 가지 핵심 가치를 제공한다. Go가 추구하는&lt;b&gt;&lt;i&gt; '명시적인 것이 암시적인 것보다 낫다&lt;/i&gt;'&lt;/b&gt;는 철학이 포인터를 통해 잘 구현되어 있음을 알 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Backend/  Go</category>
      <author>Hugehoo</author>
      <guid isPermaLink="true">https://junior-datalist.tistory.com/401</guid>
      <comments>https://junior-datalist.tistory.com/401#entry401comment</comments>
      <pubDate>Sun, 5 Jan 2025 00:48:43 +0900</pubDate>
    </item>
    <item>
      <title>더 똑똑한 기술 블로그 운영하기 (feat. 카일스쿨)</title>
      <link>https://junior-datalist.tistory.com/400</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;예상 독자&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;글쓰기의 벽을 마주한 4년차 기술 블로거의 &lt;b&gt;고민&lt;/b&gt;과 향후 &lt;b&gt;Action Item&lt;/b&gt; 을 읽고 싶은 분&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;카일스쿨의 인프런&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EA%B8%B0%EC%88%A0-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EA%B8%80%EC%93%B0%EA%B8%B0/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; &quot;실용주의 기술 블로그 글쓰기&quot;&lt;/a&gt; 강의 후기가 궁금하신 분&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;서론 : 강의 수강 계기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;기술 블로그를 운영한지 어연 4년차,&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;관성으로 블로그를 운영하고 있지만 스스로 운영 방식이나 글쓰기 파이프라인에 대해 깊은 고민을 해 본 경험이 적다. 그간 글또 운영 기간에만 글을 발행한 것은 아니니 강제성이 없어도 글은 잘 발행하는 편이라 생각하지만, 그 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;빈도나 주기가 불규칙하여&lt;/b&gt;&lt;/span&gt; 스스로 글쓰기 시스템을 만들고 싶었다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그리하여 시스템 없이 냅다 글 부터 쓰기 바빴던 습관을 조금씩 체계화 하고자 실용주의 글쓰기 강의를 수강하게 됐다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;수강 후 모든 내용을 정리한 것은 아니며, 내가 4년동안 블로그를 운영하면서 느꼈던 어려움이나, 새롭게 깨달은 점을 위주로 기록/정리했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;꾸준한 기술 블로그 운영이 어려운 이유&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 자신감이 시간의 흐름에 따라 떨어지게 됨&amp;nbsp;&lt;br /&gt;- 글쓰기에 어려움을 느끼는 것은 당연하지만 멈추지 않고 끊임없이 쓰는것이 중요하다.&lt;br /&gt;- 창작할 땐 저항감이 자연스레 찾아오기 마련&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스스로의 성장을 위한 액션을 취할 때, 모종의 필연적인 저항감을 느끼기 마련이다. 이 저항감은 늘 내면에 존재하며 본인 스스로에 의해 지속된다. 특히나 높은 차원으로 발전하는 과정에서 생기는 저항감은 두려움이 클수록 그 감정의 요동도 커지기 마련이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;문제를 해결하는 첫 번째 방법은 문제를 인식하는 것이다. 본인이 느끼는 두려움을 인정하고 그 실체를 명확히 파악하는 것 부터 시작해야 한다. 아래는 강의에서 언급한 글을 쓸 때 겪는 어려움과 그에 따른 필자의 생각을 덧붙였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;글쓰기 중 겪는 6가지 어려움&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #ef5369;&quot;&gt;1. 꾸준함&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;꾸준히 글 작성하는 것은 쉽지 않다 -&amp;gt; 습관형성의 문제&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;글을 써야한다고 생각하지만 습관은 생각만으로 형성되지 않는다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #ef5369;&quot;&gt;2. 결과물이 만족스럽지 않음&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그럼 만족스러운 글이 무엇인가? 만족의 기준은 무엇인가를 생각해볼것.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #ef5369;&quot;&gt;3. 소재&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;갑자기 떠오르지 않음. 자기검열도 생기기 마련.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;회사 일을 그대로 올리면 안되고, 가공이 필요.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #ef5369;&quot;&gt;4. 사람들이 아무도 안봐요&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;SEO 관점에서 생각해보자. 하다못해 링크드인이나 슬랙 채널에 공유하는 시도라도 해봐야 한다. 글또 같은 글쓰기 커뮤니티에 참여하는 것도 매우 효과적인 방법이라 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #ef5369;&quot;&gt;5. 걍 쓰기 싫어요&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;번아웃, 글태기 -&amp;gt; 본인 인생의 에너지가 적어지는 시기에 발생한다. 회사를 다니면서 퇴근하고 꾸준히 글을 발행하는 건 엄청난 에너지를 필요로 한다. 본인의 에너지 레벨을 고려하지않고 무작정 쓰려고 한다면 글쓰기에 대한 괜한 저항감만 커질 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #ef5369;&quot;&gt;6. 내가 감히 블로그를..?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;남들이 뭐라하지 않을까요? -&amp;gt; 자신을 가두는 용어를 쓰면 자존감이 떨어지기 마련이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;사실 아무도 관심없는데 타인을 의식하게 되는 경우도 많다. 나는 향로님, 성윤님이 아니다. 많은 이가 볼까봐 두려워하기 전에 일단 뭐라도 써보려하자.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;자신만의 글쓰기 방식을 찾아야 한다.&lt;br /&gt;본인의 에너지 레벨을 고려하고 어떤 목적 과 소재로 글을 쓸 것인지 시스템을 만들자.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;nbsp;꾸준함을 만드는 구조/환경이 중요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; background-color: #f6e199;&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;꾸준함 = &lt;/span&gt;환경  &lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; background-color: #f6e199;&quot;&gt;&amp;times;&lt;/span&gt;&lt;/b&gt; 동기부여&amp;times; 실행&lt;/span&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;기술 블로그 운영 목적&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;최근 글또 커피챗에서도 나온 주제인데 '왜 글을 쓰는지' 스스로 되물어보는게 좋다. 처음 블로그를 운영하게 된 계기 를 생각해보자. 미래의 내가 까먹지 않기 위한 기록일 수도 있고, 함께 일하는 동료에게 자료를 제공하기 위한 목적일 수도 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;&quot;&gt;내 경우엔 유용한 컨텐츠를 생산할 수 있는 사람이 되고 싶어 블로그 운영을 시작했다. 컨텐츠 생산의 목적이 타인에게 도움을 주는 것이든 &lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333; text-align: start;&quot;&gt;개인 브랜딩이든, 내재화된 명확한 동기가 있었기에 4년간 꾸준히 글쓰기를 유지할 수 있었다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333; text-align: start;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;강의에서 분석한 &lt;span style=&quot;color: #ef5369;&quot;&gt;&quot;괜찮은 블로그 포스팅 20개&quot;&lt;/span&gt; 분석한 결과&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 문제 제시 (ex, Uber Engineering blog)&lt;br /&gt;- 문제 해결 방법을 여러가지 제시 -&amp;gt; 하나 선택&amp;nbsp;&lt;br /&gt;- 특정 문제의 중요성을 공유 (케이스 공유)&lt;br /&gt;- 프로젝트 진행하게 된 배경 , 도전 과제 정의 (doordash engineering blog)&lt;br /&gt;- 요구사항&lt;br /&gt;- 구체적인 예시&lt;br /&gt;- 도식화 -&amp;gt; 흐름을 파악하기 유용함&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;강의에서는 Uber와 Doordash 엔지니어링 블로그를 예시로 좋은 글의 특징을 설명하는데 문제 해결 관점에서 두 블로그는 공통된 특징을 가진다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이는 내가 느끼기에도 크게 다르지 않았는데 회사의 규모/유명세와 관계없이 문제 상황을 명확히 설명하고, 어떤 단계를 거쳐 문제 해결에 도달했는지 자세히 설명해주는 블로그가 오래 기억에 남았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bly2e8/btsLesDHtFM/ZqJMzj2fK32dDYatw3YJKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bly2e8/btsLesDHtFM/ZqJMzj2fK32dDYatw3YJKK/img.png&quot; data-alt=&quot;'글에 단순 기술 내용만 있으면 오픈소스와 크게 다르지 않다' by 카일스쿨&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bly2e8/btsLesDHtFM/ZqJMzj2fK32dDYatw3YJKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbly2e8%2FbtsLesDHtFM%2FZqJMzj2fK32dDYatw3YJKK%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;3840&quot; height=&quot;2160&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;'글에 단순 기술 내용만 있으면 오픈소스와 크게 다르지 않다' by 카일스쿨&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위에서 언급한 특징도 모두 중요하지만 가장 중요한건 글에 본인의 생각이 들어있어야 가치가 생긴다. 테크 블로그에 본인의 생각을 어떻게 넣을 수 있을까? 문제를 해결할 때 다각도로 생각하다보면 여러 선택지가 생기는데, 선택과 결정 과정에서 본인의 의사결정에 따라 생각을 담을 수 있다.&amp;nbsp;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;글쓰기 프로세스 (글쓰기 파이프라인)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;본인의 글쓰기 프로세스를 정리하고 어디서 &lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333; text-align: start;&quot;&gt;보틀넥이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;발생하는지 파악해야한다. 파악된 후엔 개선하고 다시 시스템을 정의하면 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;필자의 파이프라인은 '소재가 떠오르면 우선 쓰기'였다. '나중에 이 소재로 적어야지~' 하고 기록만 해두면 대부분의 경우 그냥 덮어두고 살아가더라. 하지만 무작정 쓴다고 해서 늘 글이 잘 써지는 것도 아니었다. 글을 쓰기 시작했다고 해서 해당 소재에 대해 잘 모르는 경우도 빈번히 발생했다. 이런 경우 글쓰기와 공부를 병행하게 되는데 글 완성까지의 시간이 크게 늘어나 비효율적이라 느끼는 경우도 많았다. 즉 본인의 글쓰기 파이프라인은 아래처럼 개선할 수 있다. 아직 시작단계의 파이프라인이라 허접해보인다. 여기에 퇴고나 LLM 을 통한 피드백 과정도 추가하는 식으로 파이프라인을 더 개선할 수 있을 것으로 보인다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1950&quot; data-origin-height=&quot;1296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c90wVp/btsLiv08QAP/mqW5Bh0CWH0CmQLEu0eNd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c90wVp/btsLiv08QAP/mqW5Bh0CWH0CmQLEu0eNd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c90wVp/btsLiv08QAP/mqW5Bh0CWH0CmQLEu0eNd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc90wVp%2FbtsLiv08QAP%2FmqW5Bh0CWH0CmQLEu0eNd1%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;1950&quot; height=&quot;1296&quot; data-origin-width=&quot;1950&quot; data-origin-height=&quot;1296&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;글의 목차와 구조 잡기&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;강의에서 얻은 좋은 꿀팁 중 하나는 매크로를 활용하여 글의 구조를 자동화하는 것이다.(e.g, espanos) 좋은 글은 읽기 쉬운 글이며 읽기 쉬운 글은 목차와 주제가 명확하다. 즉 좋은 글을 쓰기 위한 첫 단계가 전체적인 목차를 정하는 것이기에 자동화를 활용해 글의 구조나 템플릿 잡는 과정의 허들을 낮춰보자.&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;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;글작성 템플릿 예시&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733901603427&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- 글을 읽은 독자가 어떤 변화를 하길 바라는가?
  - 
- 초안 목차 구성
  - 
  -

# Intro

&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;

# 본문


# 정리

&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;

- 글 작성하는데 걸린 시간 :
  - 하고자 하는 이야기, 개요 정리 : 
  - 초안 글 작성 : 
  - ChatGPT와 셀프 글 피드백 : 
  - 2차 글 작성 : 
  - 이미지 그리기 : 
  - 보상 :&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WTmaX/btsLeK5y1up/iTStJhwkGbrKAg0ah4AnoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WTmaX/btsLeK5y1up/iTStJhwkGbrKAg0ah4AnoK/img.png&quot; data-alt=&quot;목차 구조 잡기 -&amp;amp;gt; 작게 시작하기. 각 목차에서 독자에게 이야기할 내용 1줄로 말하면(중요한 것 1개)? -&amp;amp;gt; 그 중요한 1줄에서 살을 붙이는 방식으로 글을 이어나간다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WTmaX/btsLeK5y1up/iTStJhwkGbrKAg0ah4AnoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWTmaX%2FbtsLeK5y1up%2FiTStJhwkGbrKAg0ah4AnoK%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;3840&quot; height=&quot;2160&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;목차 구조 잡기 -&amp;gt; 작게 시작하기. 각 목차에서 독자에게 이야기할 내용 1줄로 말하면(중요한 것 1개)? -&amp;gt; 그 중요한 1줄에서 살을 붙이는 방식으로 글을 이어나간다&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;글쓰기 전략&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;파이프라인과 별개로 글쓰기 전략도 필요하다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;어떤 목적을 달성하고자 하냐에 따라 전략도 다르기 때문이다. 나는 왜 기술 블로그를 운영하는지, 지금의 나는 무엇이 중요한지 물음을 던지며 스스로의 글쓰기 전략을 세워보자. 사실 파이프라인과 전략에 명확한 차이가 무엇인지는 한 문장으로 설명하기 어려운 것 같다. 파이프라인이 글을 쓰기 위한 행동 절차에 관한 내용이라면, 전략은 글을 쓰면서 가져야 할 마음가짐에 대한 내용일까 ㅇㅅㅇ.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/corkX2/btsLg8sbxFg/z1rpfmvKGTJYlxikpAozQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/corkX2/btsLg8sbxFg/z1rpfmvKGTJYlxikpAozQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/corkX2/btsLg8sbxFg/z1rpfmvKGTJYlxikpAozQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcorkX2%2FbtsLg8sbxFg%2Fz1rpfmvKGTJYlxikpAozQk%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;3840&quot; height=&quot;2160&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;앞으로 Action Item.&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. 블로그 운영 이유를 명시적으로 블로그에 남겨두기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 공지사항이나 블로그 소개란에 기록해두면 좋겠다. 초심을 잃을 때 다시 읽어보면서 방향성을 조정하거나 목적을 재조정.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. 내가 읽었을 때 좋았던 글, 사람들이 추천하는 글을 분석해보기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 분석한 글의 형식과 방향을 토대로 나만의 글 쓰기 템플릿을 만들어보자.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. 글쓰기 프로세스, 파이프라인 정리 및 도식화하기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 주기적으로 파이프라인을 개선하자. 이것 역시 공지사항에 명시해두면 스스로의 글쓰기 방식에 대한 메타인지를 유지할 수 있을 것이라 생각된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;4. 글을 작성할 환경 만들기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 본인이 가장 잘 집중할 수 있는 환경을 찾고, 되도록 그 환경에서 글쓰기에 몰입하기. 환경이 갖춰지면 저항감이 줄어들 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;5. 글쓰기 전략 만들기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;6. 위 내용을 꾸준히 실행하기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 결국 글 쓰기도 습관이고, 습관은 곧 행동이다. 행동없는 고민을 경계하자!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;강의를 듣고나서 우선 써내려가기 바빴던 지난 날의 글쓰기 습관을 되돌아 볼 수 있었다. 스스로는 글쓰기에 대한 좋은 습관이 자리잡았다고 생각했지만 사실 그건 습관이라기 보다 어떤 인정요구에서 비롯된 비주기적인 행동이 아니었을까. 창작을 할 때 필연적으로 느끼게 되는 저항감을 피하려고만 하지 말고 그 실체에 대해 차분히 생각하며 나만의 글쓰기 습관, 파이프라인을 정립할 수 있도록 시도해봐야겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;&quot;&gt;  reference&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EA%B8%B0%EC%88%A0-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EA%B8%80%EC%93%B0%EA%B8%B0/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;&quot;&gt;- 인프런 : 실용주의 기술 블로그 쓰기&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category> 기록</category>
      <author>Hugehoo</author>
      <guid isPermaLink="true">https://junior-datalist.tistory.com/400</guid>
      <comments>https://junior-datalist.tistory.com/400#entry400comment</comments>
      <pubDate>Fri, 13 Dec 2024 15:57:38 +0900</pubDate>
    </item>
    <item>
      <title>네 번째 회사에 이르기 까지</title>
      <link>https://junior-datalist.tistory.com/399</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;SE-27737310-f7c6-4850-8d2b-c631fae2b6c6&quot; data-a11y-title=&quot;본문&quot; data-compid=&quot;SE-27737310-f7c6-4850-8d2b-c631fae2b6c6&quot;&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;SE-27737310-f7c6-4850-8d2b-c631fae2b6c6&quot; data-unitid=&quot;&quot;&gt;
&lt;div id=&quot;SE-c68736d5-0045-43a3-b3c8-35180c629ced&quot;&gt;
&lt;p id=&quot;SE-114b6fa6-dad5-4b9b-94cb-06e9eeee4545&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-665e333e-b0e9-4cb2-9286-fb592076dbd9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;i&gt;2022년 8월 8일&lt;/i&gt;은 두 번째 입사 보다 강남 대홍수의 날로 또렷이 기억한다. 물난리 속에 사당에 자리 잡은 고시원으로 몸을 옮겼고, 내 몸 하나 딱 채울 수 있는 작은 침대와 적어도 이 물난리에 쓰러질 일 없는 고시원 천장에 감사하며 서울의 삶을 시작했다. 그게 이토록 지난한 서울살이의 시작일 것이라곤 생각도 못 했다. &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-b7a0a1c4-e72a-4ce3-bf45-027a4d71b35b&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-9892c65d-ab86-464d-90a9-b3d05d755a6b&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;i&gt;2024년 11월 25일&lt;/i&gt;, 네 번째 회사의 최종 오퍼 메일을 받았다. 10월 초의 서류 접수를 시작으로 근 2달간의 채용 과정을 마무리할 수 있었다. 다른 회사의 전형은 아직 끝나지 않았지만 오퍼를 받은 회사는 이전부터 관심을 가진 곳이라 최종 선택하게 됐다. &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;생각보다 &lt;/span&gt;이직 기간이 길어지면서 정신적으로 지치는 것도 느껴 여기까지 하는 게 맞다고 생각했다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-c5205e85-7feb-47f6-a716-38605ee2a729&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-b2328c82-29d7-4900-ba86-cbacfae8c21f&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;2년 전 첫 이직 회고를 작성했는데 어느덧 네 번째 회사의 입사를 앞두고 있다. 2년 사이에 무슨 일이 있었던 걸까,,&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-aef0d5c2-2475-42ab-b1d1-d19cbc6cca82&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;첫 이직과 달리 이번 이직은 글또가 차지하는 부분이 크다. 서울에서 지낸 2년 동안 글또가 꽤나 크게 자리 잡고 있음을 이번 회고로 다시 실감한다. 이 글은 스스로를 위해 남기는 기록인 동시에 지난 2년간 나를 성장시켜준 글또에 감사함을 담은 글이다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-0bef08c6-3175-4b36-bcb5-2847f4240e3b&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;SE-4c35d762-853b-4701-92f7-51b8ed4d98ab&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;동일한&amp;nbsp;&lt;s&gt;이직&lt;/s&gt; 퇴직 사유&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p id=&quot;SE-4e57b3ff-7ccf-452d-b9b7-62bf2999b001&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;올해 6월 세 번째 회사를 퇴사했다. 회사의 경영악화로 1년 2개월의 재직 기간을 마무리하고 회사를 떠났다. 두 번째 회사도 동일한 사유로 퇴직했기에 권고사직 통보도 무덤덤하게 받아들였다. CTO님이 미안한 표정으로 1on1 을 부른 순간부터 예상은 하고 있었다. 덤덤하게 권고사직 통보를 받아들이고 손에 들린 서류를 챙겨 나왔다. 친하게 지내던 인사팀 친구랑 허심탄회하게 대화를 나눈 후 짐을 싸서 집으로 향했다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-1348d8cb-151f-4aa1-a813-21a09c208acc&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;며칠 후 최종 퇴직 서류를 작성하러 마지막으로 회사에 갔다. 괜히 팀원들 마주치면 어색해질까봐 서류만 제출하고 튀고 싶었는데,, 다들 우루루 몰려나와서 인사해 주더라. 우씨 눈물 참느라 힘들었다. 1년 조금 넘긴 세 번째 회사 생활을 그렇게 마무리했다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-8b0033ab-17d9-42ab-82bd-cfdcb5bce423&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;SE-7379f340-c8ec-4756-8593-e001c59b0ec5&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;지원할 회사 선정&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p id=&quot;SE-239977e6-6fd2-436f-8a1c-a88ff684a67b&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;4년 차라는 짧은 경력 동안 총 3곳의 회사를 다녔다. 첫 회사는 대기업과 중견기업 사이의 걸친 곳으로 고용안정성은 최고였지만 보수적이고 폐쇄적인 기업 문화 탓에 개발자 친화적인 곳에서 일하고 싶다는 (어린) 생각으로 퇴사했다. 다음 재직한 두 회사는 스타트업인데, 두 곳 모두 경영악화로 인한 구조조정으로 퇴사했다. 첫 회사를 다닐 때만 해도 고용안정성이 이토록 쉽게 위협받을 줄 몰랐는데 역시 인생은 실전이었다. 일전의 경험을 양분 삼아 앞으로 지원할 회사의 기준에 &lt;b&gt;재무 안정성&lt;/b&gt;이나 &lt;b&gt;고용 안정성, 페이, 인센티브&lt;/b&gt; 등도 고려하게 됐다. 테크 위주의 대기업과 더불어 스타트업 씬에서도 재무적으로 탄탄하거나 투자 라운드를 성공적으로 마친 회사를 위주로 지원할 회사를 선정했다.&lt;br /&gt;&lt;b&gt;기술 스택&lt;/b&gt;은 크게 개의치 않았다. JVM 위주의 커리어를 쌓았지만 그게 비즈니스를 잘한다는 뜻은 아니기에 Node.js 나 Python 을 사용하는 회사도 열린 마음으로 알아봤다. 선택의 폭은 넓어졌지만 반대로 JVM 경력만 있는 나를 굳이 뽑을 필요가 있을까 라는 생각도 들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;개인적으로 &lt;b&gt;테크 블로그&lt;/b&gt;를 운영하는 회사도 희망했다. 글또를 해서 그런지 블로그를 운영하는 회사에 더 눈길이 갔다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 id=&quot;SE-cc6ba61b-b92e-4837-827f-899c7a8f3080&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;이직 준비&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-50907789-c2af-4e1e-8af3-e12a4b9dee59&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;이력서 갈아엎기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-4529f217-2094-4ebd-a037-2110c523e375&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;감사하게도 커피챗 한 회사의 VP 분께서 이력서를 직접 검토해 주셨는데 이력서 내용이 아쉽다는 피드백을 받았다. 솔직한 언어로 가감 없이 표현해 주셔서 오히려 좋았다. 피드백을 계기로 이력서를 지속적으로 뜯어고쳤다. 같은 내용을 수없이 반복해서 읽었고 검토자 입장에서 쉽게 쓰인 이력서를 작성하려 했다. 그럼에도 여전히 아쉬움이 남는 이력서라 느꼈다. 소위 네카라쿠배 같은 좋은 회사에 다니시는 분들은 어떻게 이력서를 작성하는지 궁금하던 찰나에 좋은 기회로 이력서 작성 강의를 수강할 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-65ba9458-ac89-450f-a133-984c3607806c&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.inflearn.com/course/%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%9D%B4%EB%A0%A5%EC%84%9C-%EC%9E%91%EC%84%B1%EB%B2%95-%EA%B0%95%EC%8A%B9%ED%98%84&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; 인프런 - 이력서 작성 강의&lt;/a&gt;를 수강하면서 이력서를 보완했고 지식 공유자 님의 피드백 덕분에 현재 이력서를 객관적으로 판단할 수 있었다. 아래 이미지를 보면 4개의 버전을 거치면서 이력서를 보완한 것을 볼 수 있다. (버전 1 이력서를 오랜만에 다시 봤는데... 넘어갈게요  ) &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이력서 분량만 해도 눈에 띄게 줄어든 것을 볼 수 있다. 줄이고 줄여 두 장 내에 액기스되는 부분만 담으려 했다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-6c0dded2-1812-4472-a7cb-c68584b29f08&quot; data-a11y-title=&quot;사진&quot; data-compid=&quot;SE-6c0dded2-1812-4472-a7cb-c68584b29f08&quot;&gt;
&lt;div&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;SE-6c0dded2-1812-4472-a7cb-c68584b29f08&quot; data-unitid=&quot;&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;SE-6c0dded2-1812-4472-a7cb-c68584b29f08&quot;&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;&quot; data-unitid=&quot;SE-6c0dded2-1812-4472-a7cb-c68584b29f08&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;1560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CLjNB/btsK2ou1eva/aiFXpkdgHWsNqAfayIAKEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CLjNB/btsK2ou1eva/aiFXpkdgHWsNqAfayIAKEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CLjNB/btsK2ou1eva/aiFXpkdgHWsNqAfayIAKEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCLjNB%2FbtsK2ou1eva%2FaiFXpkdgHWsNqAfayIAKEk%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;693&quot; height=&quot;1560&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;1560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-fbea2b6b-b937-4c7a-9e23-814fc54d2ef7&quot; data-a11y-title=&quot;본문&quot; data-compid=&quot;SE-fbea2b6b-b937-4c7a-9e23-814fc54d2ef7&quot;&gt;
&lt;div&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;SE-fbea2b6b-b937-4c7a-9e23-814fc54d2ef7&quot; data-unitid=&quot;&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;SE-c2769d1d-c27a-4de7-b0f9-6035bb1ceda4&quot;&gt;
&lt;p id=&quot;SE-65b87a32-bcfa-496b-9639-1dae03571558&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;피그마로 지식 공유자 님의 피드백을 확인하면서 이력서를 점차 보완했다. 좋았던 점은 추상적이거나 애매한 표현을 더 객관적으로 보완하게 된 점인데, 나만 이해되는 문장이나 표현은 삭제하면서 누가 읽어도 이해할 수 있는 정량적이고 객관적인 문장을 작성하도록 신경 썼다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-52789734-7a0a-4122-9a9a-3bd55f10ec1e&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이미 회사를 나왔기에 이력서 내용 자체는 바꿀 수 없었으니 잘 포장하는 게 관건이라는 사실을 받아들였다. 아쉬운 이력이라 해서 아쉬운 채로 둔다면 아무도 거들떠보지 않기에,,, 어떻게든 눈에 띄도록 (적당히) 잘 포장하는 법을 배운 것 같다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이력서 피드백 덕분에 그런대로 읽을만한 이력서로 탈바꿈 할 수 있었다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-f90b9f4c-fb02-4017-bf51-9f98419d308a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-38e9ab59-3e1d-4281-9d4e-8ed1a2272109&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;면접 스터디&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-960c959a-7eec-4a7c-a681-310dccd961c1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;마지막 면접 경험이 거의 일 년 전이라 어떻게 준비해야 할 지 막막했다. 제대로 준비하려면 비슷한 환경의 사람들과 방향성을 맞추는 것이 낫다고 판단하여 면접 스터디에 참여했고 총 4인의 면접 스터디를 진행했다. 스터디원 대부분은 0~1년 차였지만 이력서 내용은 웬만한 2~3년 차 보다(=나) 나아 보였다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-36f36ec2-7e80-439f-b195-5e08b9df7256&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;각자의 이력서를 노션에 업로드 후 경력 혹은 프로젝트 사항에 대해 다른 스터디원들에게 설명하는 시간을 가졌다. 스스로의 이력을 구두로 설명하며 이력서를 객관적으로 되돌아볼 수 있었고 이를 바탕으로 반복적으로 이력서를 수정/보완했다. 스터디원분들에게 쉽게 설명하지 못한 이력은 실제 면접에서도 단번에 설명하기 어려운 내용이라 판단해 모두 가감 없이 잘라내고 수정했다. 수정에 수정을 반복할수록 면접에서도 설명하기 쉬운 이력서로 점차 개선되는 걸 느꼈다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-442af815-593d-4964-8aee-63a0d72189e6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;이력서가 보완되면서 면접 일정이 점차 많이 잡혔다. 노션에 캘린더를 추가하여 스터디원들의 면접 일정을 서로 공유했다. 면접이 다가오면 서로 응원했고 면접 종료 후엔 복기하면서 어떤 질문이 나왔는지 공유하는 시간을 가졌다. 이 방식이 꽤 많은 도움이 됐는데 다른 분이 받은 질문을 참조하면서 내 이력서를 기반으로는 어떤 질문이 나올지 스스로 생각해 볼 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-0de7a062-d21f-4bfb-9e61-800c6765d938&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;결과적으로 네 명 모두 이직/취직에 성공했고 다들 어엿한 직장인이 됐다. 최근엔 강남에서 다 같이 모여 쫑파티도 했는데 표정에 늘 그늘이 져있던 취준 시절보다 다들 밝아 보였다. 이런 사람이었나 싶을 정도로 새로운 모습을 본 것 같아 신기했고 한편으론 취업 준비가 우리를 얼마나 옥죄고 있었나 씁쓸하기도 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-1e303769-d656-4b1f-9186-5b785f9a28d1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-1c6be217-17c3-4e6e-87c7-fad151acbf8e&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;새로운 언어 학습: Golang&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-f3004341-f96c-4c7b-b195-275212a736ba&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;작년 GopherCon 을 계기로 Go 언어에 꽂혔다. 올해 초부터 튜토리얼 영상을 보면서 천천히 시작했는데 퇴사한 김에 좀 더 본격적으로 학습하게 됐다. 한국 개발 시장에서 Java/Kotlin 은 자연스럽게 접할 수 있지만 Go는 직접 관심을 갖지 않는 이상 앞으로도 사용할 일이 적을 것이라 생각했다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-cbb0e081-34c2-48bf-8cb4-a31b5d4ba3c3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;꼭 이직에 도움 되기 위한 용도로 새로운 언어를 배운 건 아니었는데, 결론적으로 최종 오퍼를 수락한 회사는 Golang 을 사용하는 회사가 됐다. 전략적으로 접근한 건 아니지만 Go 언어를 배운 흔적이라도 있는 점이 회사 입장에서는 가산점이 아니었을까 생각해 본다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-23cfca80-99fd-4794-9672-721dd1e4a104&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-3c11abb2-e9a1-4475-a52d-0759011cfde9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;책 / 논문 스터디와 기술 서적 읽기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-950241b9-fb96-4e87-9f55-85ae7b3e3033&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;퇴사하고 얼마 되지 않아 글또 친구들과 술자리에서 스터디 제안을 받았다. 덕분에 여러 대단한 분들과 함께 &lt;b&gt;데이터베이스 인터널스&lt;/b&gt;&amp;nbsp;책 스터디를 진행할 수 있었다. 두 달 전에 책 스터디는 마무리했고 현재는&amp;nbsp;&lt;i&gt;(&lt;b&gt;Computer Science의 역사를 바꾼)&lt;/b&gt;&lt;/i&gt;&lt;b&gt;&amp;nbsp;논문 리딩 스터디&lt;/b&gt;로 이어서 진행 중이다. 이 스터디를 통해 특정 지식보다 학습 방향성에 대해 더 생각하고 배우게 됐다. 스터디는 한 주 동안 학습한 내용에 대해 자유롭게 의견을 주고받는 식으로 진행됐는데, 정작 나는 정리하기에만 바빠 내 의견을 투영하진 못했다. 그저 읽고 정리하기 바빴던 정리봇 1이었던 것.. 반면 주로 스터디를 진행하시는 도진님이나 동인님은 전체적인 내용을 파악하며 정리하고, 중간중간 본인의 생각도 곁들이면서 스터디를 진행해 주셨다. 덕분에 단순 정리에서 벗어나 지식에 주관을 투영하면서 나만의 지식 체계를 구축하는 시도를 하게 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-75cdd14c-cc05-4c0f-a589-7f0f10bb99cc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-47561faf-9a7c-48f8-9b76-e5cad1babf07&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이전부터 관심은 가졌지만 접할 기회가 없던 기술 책도 여럿 읽었다. 그중 하나가&amp;nbsp;&lt;b&gt;쿠버네티스 개발 전략&lt;/b&gt;인데 멘토가 후배에게 건넬 법한 친근한 어조로 쓰인 책이라, 쿠버네티스라는 낯선 영역을 쉽게 진입할 수 있었다. 백엔드 개발자가 쿠버네티스를 어느 정도까지 알아야 하는지 기준이 모호하다 느꼈는데, 이 책을 통해 쿠버네티스의 구성요소와 기본적인 동작 방식 그리고 백엔드 관점에서 알아야 할 쿠버네티스의 개론적인 내용을 살펴볼 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-0680892d-1956-4810-b251-f46f36ca8aa0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;그 외에 스터디 책으로 읽은 &lt;b&gt;데이터베이스 인터널스&lt;/b&gt;는... 사실 데중애설 만큼 어려워서 내년에 한 번 더 읽을 생각이다. 지금 스터디원들과 한 번 더 읽을 수 있으면 좋겠다. 그때는 나도 좋은 인사이트를 많이 나눠줄 수 있도록 잘 준비 하고 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-597f8b51-20b9-47ec-a00f-1b1ae90c2267&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-cf3c91a6-5ecf-4c4c-bb3c-66801a7bf24b&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;개인 서비스 운영&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-c9986417-85ad-478d-9f2c-fbc0c5d992a9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;작고 귀여운 &lt;a style=&quot;color: #000000;&quot; href=&quot;https://blog-scrapper-ui.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; 개인 서비스&lt;/a&gt;를 출시했다. 테크 블로그 모아보기,,라는 여전히 마땅한 이름을 갖지 못한 서비스를 9월부터 운영 중이다. 새로 배우기 시작한 Go 언어로 백엔드를 구축하고 AWS Lambda를 활용해 배포한 만큼 서버 비용 부담 없이 서비스를 개발할 수 있었다. 약간의 프론트 지식으로 next.js를 활용한 UI 개발 경험도 쌓을 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-4b76bd0b-2b4f-4db9-a3bf-528b9f559721&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;'이걸 누가 쓰겠어 나만 써야지 깔깔~' 생각하여 배포도 잘못하고, 라이브 서버에 부하 테스트 날려 서버 다운시킨 적도 있었는데... 글또에서 은근히 찾아주시는 것 같아 경각심을 가지며 다시 열심히 개발하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-6c54b975-572e-46d8-9d2b-4afd4e82dab2&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-6c1bbcdb-8085-42c3-b0d4-4d82986afba5&quot; data-a11y-title=&quot;사진&quot; data-compid=&quot;SE-6c1bbcdb-8085-42c3-b0d4-4d82986afba5&quot;&gt;
&lt;div&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;SE-6c1bbcdb-8085-42c3-b0d4-4d82986afba5&quot; data-unitid=&quot;&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;SE-6c1bbcdb-8085-42c3-b0d4-4d82986afba5&quot;&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;&quot; data-unitid=&quot;SE-6c1bbcdb-8085-42c3-b0d4-4d82986afba5&quot;&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/8wPs5/btsK25hbvJv/H9JU9ptfsHkVK0ocxfvCRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8wPs5/btsK25hbvJv/H9JU9ptfsHkVK0ocxfvCRk/img.png&quot; width=&quot;334&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1066&quot; data-is-animation=&quot;false&quot; style=&quot;width: 38.8369%; margin-right: 10px;&quot; data-widthpercent=&quot;39.29&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8wPs5/btsK25hbvJv/H9JU9ptfsHkVK0ocxfvCRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8wPs5%2FbtsK25hbvJv%2FH9JU9ptfsHkVK0ocxfvCRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;1066&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5IYw1/btsK2TBhlfB/Lqjxkon5tFiBNQQZy0mpm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5IYw1/btsK2TBhlfB/Lqjxkon5tFiBNQQZy0mpm0/img.png&quot; width=&quot;515&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;690&quot; data-is-animation=&quot;false&quot; style=&quot;width: 60.0003%;&quot; data-widthpercent=&quot;60.71&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5IYw1/btsK2TBhlfB/Lqjxkon5tFiBNQQZy0mpm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5IYw1%2FbtsK2TBhlfB%2FLqjxkon5tFiBNQQZy0mpm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-9e940cd8-4f6d-4281-89e1-044b919a5034&quot; data-a11y-title=&quot;사진&quot; data-compid=&quot;SE-9e940cd8-4f6d-4281-89e1-044b919a5034&quot;&gt;
&lt;div&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;SE-9e940cd8-4f6d-4281-89e1-044b919a5034&quot; data-unitid=&quot;&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;SE-20d71d4b-e2d0-43a3-bdeb-641880792409&quot; data-a11y-title=&quot;본문&quot; data-compid=&quot;SE-20d71d4b-e2d0-43a3-bdeb-641880792409&quot;&gt;
&lt;div&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;SE-20d71d4b-e2d0-43a3-bdeb-641880792409&quot; data-unitid=&quot;&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;SE-8513ede2-d06a-46aa-a203-1128374d45cf&quot;&gt;
&lt;p id=&quot;SE-1faf3e53-e999-46c2-a6b3-4e8cf733d09c&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-83ed90b6-72ee-487e-9332-bfe526006cf2&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(실패) 포트폴리오 제작&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-a6ea183f-2631-4f13-8517-ca5ffd9433f4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;Go를 사용하여 대용량 스트리밍 채팅 서버를 만들고 싶었다. 실제로 채팅 인프라를 구축하기 위해 카프카 클러스터도 직접 만들어보고, 프로듀서, 컨슈머, MongoDB까지 모두 세팅했다. 부하 테스트를 날리면서 어디까지 트래픽을 견딜 수 있나 실험도 했지만, 실사용을 위한 것이 아닌 오직 포트폴리오만을 위한 프로젝트라 방향을 자주 잃었다. 유종의 미를 거두지 못해서 아쉬움으로 남는 프로젝트였다. 그럼에도 실시간 스트리밍 채팅 서버라는 목표를 위해 여러 방면으로 공부해서인지 아키텍처 측면에서 다양하게 공부할 수 있었는데, 면접에서 이런 부분의 내용을 답할 수 있어 다행이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-1b914674-e07b-4929-a7da-117ffe3b1815&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-5e258a19-2bfa-4381-8aaf-4326f35dc005&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;(실패) 자립 준비&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-e9c60b34-818a-43b7-a47b-9b18c2da8310&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;경영악화로 인한 퇴사를 두 번이나 겪으면서 회사에 의존하는 개발자가 되면 안 되겠다고 생각했지만,, 실제로 자립을 위한 액션은 취하지 못했다. 정말 가까운 곳에 1인 개발자의 삶을 시작한 은찬 님이 계시지만 나는 아직 회사가 필요하다고 판단했다. 백수가 되어보니 고정적인 수입이 주는 안정감을 무시하지 못하겠더라. 다시&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-5679f6ee-e0a9-4b2f-afe2-6c4430e2a940&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;시간이 많이 생긴 만큼 가시적인 결과물을 많이 만들어봤어야 했는데, 테크 블로그 모아보기를 제외하곤 마땅히 보여줄 만한 결과물이 없어 아쉽다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-af6e411c-4bd1-494f-8408-d7ed4786dad3&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-2ef7693a-5a21-4da2-b521-80955352b763&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;근데 실패는 뭐다?&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-99f3c6a7-c64d-4f24-acef-c52b763b9b85&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;다시 하면 된다 ~&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-38c27e4c-00e1-4dd6-bd91-684dcd9122e4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-8fe32330-b7c5-4386-bc9a-fea5d3620048&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;레퍼런스 체크&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-9e5a0243-263d-4b2b-9e6e-a14344ab06db&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;생애 첫 레퍼쳌이라 며칠은 혼자 속앓이 했다. 누구에게 부탁해야 할지, 어떤 내용을 적어주실지 오만 걱정이 가득했는데 정말 감사하게도 전 직장 동료 두 분과 전전직장의 팀장님께서 레퍼런스 체크를 해주셨다. 스펙터를 통해 작성하셨다는데 생각보다 쓸 내용이 많다고 하시더라. 두고두고 은혜갚는 임성후가 되겠습니다,, 그리 대단히 잘 산 인생은 아니라 생각했는데 도움 주신 많은 분들 덕분에 늘 친절하고 예의 있게 살아야겠다는 다짐을 다시금 하게 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;SE-65b3d5eb-9872-4a90-ae46-d06feaac66a8&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;최종 면접&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p id=&quot;SE-a52a226f-a6fb-4e80-a424-7b7b0cb40c1f&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;경험상 2차(최종) 면접에서는 아직 탈락한 적이 없어 어느 정도 자신이 있었는데 예전부터 정말 가고 싶었던 회사의 최종 면접이 잡히니까 면접 날짜가 다가올수록 미친 듯이 불안해졌다. 너무 간절하면 잘 안되는 경우도 있지 않은가. 별의별 생각을 할수록 스스로 말라비틀어지는 기분이었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-ba22bec1-d442-46bc-9909-6bbba000f946&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;불안할수록 빈틈없이 준비하면, 이내 불안감이 자신감으로 바뀐다는 말이 있지 않은가. 회사 기술 블로그와 컬처 블로그를 정독하면서 예상 질문 리스트를 뽑았고, 심지어 유튜브에서 회사 CEO 분들이 나온 인터뷰까지 찾아가며 궁금한 점을 차곡차곡 정리했다. 컬처 블로그를 읽으면서 회사가 필요로 하는 직원이 어떤 모습일지, 직원 인터뷰에서 이들이 원하는 미래 동료는 어떤 모습일지 생각하며 예상 질문과 답변을 준비했다. 여전히 긴장되고 떨렸지만 그래도 막연한 불안감은 들지 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-50a41e58-dac0-4754-8162-cac9cc06f7e4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;(이래놓고 떨려서 면접 당일 2시간 일찍 도착함)&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-a5b9d4d1-72ec-4d76-ba55-93f24e62ccf1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;3명의 C-level 분들과 한 시간가량 인터뷰를 진행했고 여전히 떨긴 했지만 준비한 말은 모두 할 수 있었다. 지원한 회사에 이토록 정성 들여 답변을 준비한 건 처음이라,, 합불을 떠나서 후회 없이 마무리할 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-31932f93-af0b-4b18-b549-f7f4f9a94969&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-e9849176-c2c6-45e2-9a2e-a352cc4bba7c&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;최종 면접의 킥이 무엇이라 생각하는가?&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-17a34db7-8f5d-4b8a-a5ce-b17b40a7a5ba&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;1차 면접보다 나아진 모습을 보여주는 것이라 생각하는데, 1차 때 대답하지 못한 질문을 최종 면접에서도 그대로 물어보셨다. 처음 답변을 하지 못한 건 실수일 수 있지만 두 번째부터는 실력이다. 최종에서는 같은 질문의 답을 자신 있게 뱉을 수 있을 정도로 준비했기에 CTO 분의 희미한 미소를(?) 볼 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-44a7a524-428c-4f48-ae6c-7c01d8f64ef4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;한 주가 흘러 감사하게도 합격 연락을 주셨고, 최종 오퍼를 수락하며 다섯 달의 걸친 휴직기를 마무리할 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-827e84da-8fb3-43ac-a3ed-02490f237013&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-994bd53e-4b4c-4516-b105-a17f69857136&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-63a4daf0-5973-4f76-a9cf-b52cab90a488&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;SE-1da96f6d-632d-4d15-98f9-d5ec21ff92a9&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;이직을 마무리하며&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-d79f5a2c-f34a-46d7-a58d-623971fd95c3&quot; data-a11y-title=&quot;사진&quot; data-compid=&quot;SE-d79f5a2c-f34a-46d7-a58d-623971fd95c3&quot;&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;SE-d79f5a2c-f34a-46d7-a58d-623971fd95c3&quot; data-unitid=&quot;&quot;&gt;
&lt;div id=&quot;SE-d79f5a2c-f34a-46d7-a58d-623971fd95c3&quot;&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;&quot; data-unitid=&quot;SE-d79f5a2c-f34a-46d7-a58d-623971fd95c3&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;1066&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lfnUV/btsK3hoeAkY/QCG3gMTqBiq0rWPxgpmbWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lfnUV/btsK3hoeAkY/QCG3gMTqBiq0rWPxgpmbWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lfnUV/btsK3hoeAkY/QCG3gMTqBiq0rWPxgpmbWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlfnUV%2FbtsK3hoeAkY%2FQCG3gMTqBiq0rWPxgpmbWK%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;492&quot; height=&quot;1066&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;1066&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-a1473432-95c1-4b18-940f-973c7de965b9&quot; data-a11y-title=&quot;본문&quot; data-compid=&quot;SE-a1473432-95c1-4b18-940f-973c7de965b9&quot;&gt;
&lt;div&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;SE-a1473432-95c1-4b18-940f-973c7de965b9&quot; data-unitid=&quot;&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;SE-007d4c65-fec1-4f44-b947-acef6f8d69ef&quot;&gt;
&lt;p id=&quot;SE-1f7c21e4-c00d-4330-98fa-d90144e43ee0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-8ac75f24-d758-4c4b-a765-851fbb2d90d4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;대략 30여 곳의 지원서를 넣었다. 끊임없는 탈락에 정신이 혼미해질 때도 많았는데, 항상 주변의 훌륭한 개발자 동료들 덕분에 멘탈을 잡을 수 있었다. 이력서 피드백과 추천 채용을 제안해주신 글또 분들, 레퍼첵에 도움주신 전직장 동료, 팀장님 까지. 혼자 해냈다기엔 정말 많은 분들의 도와주셨기에 얻을 수 있는 결과였다. 언젠가 나도 도움을 줄 수 있는 사람이 되길 바란다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;br /&gt;다음 회사는 가장 먼저 오퍼를 주신 곳으로 결정했다. 면접 경험도 가장 좋았고, 도메인 자체도 흥미롭게 일할 수 있는 곳이라 기대된다. 물론 막상 입사하고 나면 달라질 수도 있는게 인생이라지만 경험하기 전까진 어떠한 선택도 마찬가지 아닐까.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-152446c9-1990-406a-bee3-b279515780fd&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;네 번째 회사지만 여전히 떨리고 기대되는 건 첫 이직 때와 다르지 않다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-e240c34b-696b-404a-989c-35472102fe4b&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;처음 접하는 도메인과 새로운 언어를 사용하는 곳이라 약간 신입이 된 기분도 든다. 파이썬 원 툴이던 신입 시절, 처음 접하는 리액트와 레거시 스프링 늪에 빠져 매일같이 야근했지만 개발할 수 있단 사실 자체로 행복했던 때가 있다. 겸손하고 낮은 자세로 뭐든 배우려는 모습을 잃지 않았으면 한다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;여전히 개발이 재밌는가? 라는 질문에 1년 차 때처럼 쉽게 예쓰!를 외칠 수 없는 4년 차가 됐지만, 그럼에도, 그럼에도 천천히 의지를 다질 수 있는 사람이길 희망한다. 금방 타오르고 식어버리는 열정보다 지치더라도 묵묵히 해낼 수 있는 의지를 보이고 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=_DvVTmolY2U&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/dCgTne/hyXGEjJZl3/RH3iQoQGqCHmpdnwDjayhK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=300_364_408_482,https://scrap.kakaocdn.net/dn/cQqQsS/hyXDivUtqz/Xch64tkI2bvwfYY2LZeV8k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=300_364_408_482&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;구글이 인정한 개발자의 학생 시절 공부법&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/_DvVTmolY2U&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-d7f716b7-c91e-4c08-a460-3baadad0d9cb&quot; data-a11y-title=&quot;본문&quot; data-compid=&quot;SE-d7f716b7-c91e-4c08-a460-3baadad0d9cb&quot;&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;SE-d7f716b7-c91e-4c08-a460-3baadad0d9cb&quot; data-unitid=&quot;&quot;&gt;
&lt;div id=&quot;SE-5b440d53-7bbf-4e13-8e30-6d973ef69cb4&quot;&gt;
&lt;p id=&quot;SE-8366bb2d-10c2-474e-b54a-39d73d7fb34b&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;휴직 당시 큰&amp;nbsp;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.youtube.com/watch?v=_DvVTmolY2U&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;동기부여를  받은 영상&lt;/a&gt;. 열정보다는 의지가 충만한 사람이 되고 싶다. 금방 타오르고 식어버리는 열정보다 힘들고 지치더라도 묵묵히 걸어나가는 의지를 보이고 싶다.&amp;nbsp;&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;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p id=&quot;SE-8b253cdf-59f8-4208-a508-8b5cd446384f&quot; style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;그리 대단한 이직 이야기는 아니지만, 2년 전 처음 작성한 이직 회고를 다시 읽으니 그때보다 조금씩 성장 중인 내가 보인다. 지금의 기록도 몇 년 후 다시 읽을 때 성장의 지표가 되면 좋겠다. 앞으로는 어떻게 살아야 할지 너무 먼 미래의 계획이나 포부를 세우는 것보다, 그저 하루를 충실히 살아가는 개발자가 되고 싶다. 미래는 그리 쉽게 생각대로 되지 않고 그렇다고 해서 굳이 실망할 필요도 없다고 4년 전의 열정 가득한 신입 개발자에게 말해주고 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-64a412c0-0c74-430a-8041-6e649b05205e&quot; style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;입사한 지 2주가 흘렀다. 지금껏 경험한 회사 중 가장 만족스러운데 이는 모두 함께하는 팀원분들 덕분이라 생각한다. 확실히 입사까지 까다로운 과정을 거친 만큼 좋은 분들이 모인 회사라 느끼며 생활하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;오늘은 페어 프로그래밍 겸 매니저분과 일했는데 세팅 과정에서 잘 해결되지 않는 부분을 한 시간 넘게 같이 붙잡고 씨름해 주시더라. 고마우면서도 한편으로 나는 이 정도로 팀원에게 신경 써주지 못할 것 같다고 인정할 수밖에 없었다. 짧은 시간이지만 벌써 많이 배운다. 다음 주엔 우리 팀에 인턴분이 새로 오신다는데 나도 그분에게 좋은 영향을 줄 수 있는 사람이고 싶다. 적어도 회사에서만큼은 늘 예의 있고 신중하게 말하려 노력하자. 어렵겠지, 그래서 그만큼 더 값어치 있는 것이고.&lt;/span&gt;&lt;/p&gt;
&lt;div id=&quot;SE-35D9DAAF-6792-43E1-BA36-3BC44243D341&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;1031&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nNCep/btsLFzBvofT/776PLxiIvLwK8kPmk62go1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nNCep/btsLFzBvofT/776PLxiIvLwK8kPmk62go1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nNCep/btsLFzBvofT/776PLxiIvLwK8kPmk62go1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnNCep%2FbtsLFzBvofT%2F776PLxiIvLwK8kPmk62go1%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;594&quot; height=&quot;792&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;1031&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p id=&quot;SE-402bd880-34fd-429a-91b9-a50a8ca96422&quot; style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; font-family: 'Nanum Gothic';&quot;&gt;글또 반상회 가서 뿌려야지 희희&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-0ce0a524-ee27-4364-a1e6-96f7c8fd88c4&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p id=&quot;SE-e59c43db-d30e-41ef-bfd2-4e0f75c8b862&quot; style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;​&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-ce84f113-629f-470b-ba95-d39418c25764&quot; style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;앞으로도 필연적으로 이직 후기를 작성하는 날이 오겠지. 내 의지든 그렇지 않든 그런 날은 분명 또 오게 될 것이다. 그래도 다음 이직 후기를 적는 날이 온다면 적어도 이 회사에서 후회 없이, 내 깜냥만큼의 노력과 성실을 쏟아낸 후에 그 시간이 찾아오길 바란다. 후회 없는 삶이 어디 있겠냐마는 결국 그 선택도 나의 몫이다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p id=&quot;SE-6a778a37-1f41-4d3d-8428-9b2339e56e86&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-a5ac0286-43b8-4707-b2fc-fde54085c8df&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;SE-4caf6779-9af2-4388-840a-868f5c105d07&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;p.s thanks to 대나무숲!&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p id=&quot;SE-0ecba38d-f0cd-42ae-85c1-4f279df46299&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;지원한 회사 중 가장 가고 싶었던 곳은 광고 도메인의 B2B 회사였다. 광고 도메인은 처음이라 무엇을 준비해야 할지 막막할 때 글또 대나무숲에 고민을 올렸다. 많은 분들이 답변을 달아주셨고 면접 준비할 때 이를 바탕으로 광고 도메인이 가진 어려움과 문제가 어떤 것이 있는지 미리 파악할 수 있었다. 사실 실제 면접에서 아래의 내용을 말할 일은 거의 없었지만, 적어도 이 시장이 안고 있는 문제점이 무엇인지 파악한 채로 면접에 참여할 수 있어 더 자신감을 가질 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-539178df-1cf0-4f8b-a346-e6c20bc4671b&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-38bc4893-8243-4270-a8b0-0f623a99c5e0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;답변 주신 모든 분들께 진심으로 감사의 인사를 전합니다!&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-8a6341a2-7698-4dde-bc90-c63b321649ed&quot; data-a11y-title=&quot;사진&quot; data-compid=&quot;SE-8a6341a2-7698-4dde-bc90-c63b321649ed&quot;&gt;
&lt;div&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;SE-8a6341a2-7698-4dde-bc90-c63b321649ed&quot; data-unitid=&quot;&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;SE-8a6341a2-7698-4dde-bc90-c63b321649ed&quot;&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;&quot; data-unitid=&quot;SE-8a6341a2-7698-4dde-bc90-c63b321649ed&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d05yLO/btsK3kylM0a/pE4Eh4jZkxjqEKZYkxZOwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d05yLO/btsK3kylM0a/pE4Eh4jZkxjqEKZYkxZOwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d05yLO/btsK3kylM0a/pE4Eh4jZkxjqEKZYkxZOwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd05yLO%2FbtsK3kylM0a%2FpE4Eh4jZkxjqEKZYkxZOwk%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;693&quot; height=&quot;322&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-03602948-ae54-4a76-be04-ca47df35be41&quot; data-a11y-title=&quot;사진&quot; data-compid=&quot;SE-03602948-ae54-4a76-be04-ca47df35be41&quot;&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;SE-03602948-ae54-4a76-be04-ca47df35be41&quot; data-unitid=&quot;&quot;&gt;
&lt;div id=&quot;SE-03602948-ae54-4a76-be04-ca47df35be41&quot;&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;&quot; data-unitid=&quot;SE-03602948-ae54-4a76-be04-ca47df35be41&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;913&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rvs2Q/btsK2BANE0m/43QCkkDdeLTcNRk1Po3U9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rvs2Q/btsK2BANE0m/43QCkkDdeLTcNRk1Po3U9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rvs2Q/btsK2BANE0m/43QCkkDdeLTcNRk1Po3U9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frvs2Q%2FbtsK2BANE0m%2F43QCkkDdeLTcNRk1Po3U9K%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;693&quot; height=&quot;913&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-84314d93-50f9-4104-aea6-caf1327e5c46&quot; data-a11y-title=&quot;사진&quot; data-compid=&quot;SE-84314d93-50f9-4104-aea6-caf1327e5c46&quot;&gt;
&lt;div&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;SE-84314d93-50f9-4104-aea6-caf1327e5c46&quot; data-unitid=&quot;&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;SE-84314d93-50f9-4104-aea6-caf1327e5c46&quot;&gt;
&lt;div data-direction=&quot;top&quot; data-compid=&quot;&quot; data-unitid=&quot;SE-84314d93-50f9-4104-aea6-caf1327e5c46&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4fijQ/btsK20tvzgQ/F8i6705g098zokKLEV0SeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4fijQ/btsK20tvzgQ/F8i6705g098zokKLEV0SeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4fijQ/btsK20tvzgQ/F8i6705g098zokKLEV0SeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4fijQ%2FbtsK20tvzgQ%2FF8i6705g098zokKLEV0SeK%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;693&quot; height=&quot;924&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> 기록</category>
      <author>Hugehoo</author>
      <guid isPermaLink="true">https://junior-datalist.tistory.com/399</guid>
      <comments>https://junior-datalist.tistory.com/399#entry399comment</comments>
      <pubDate>Sat, 30 Nov 2024 14:01:23 +0900</pubDate>
    </item>
    <item>
      <title>Raft 합의 알고리즘</title>
      <link>https://junior-datalist.tistory.com/398</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;a href=&quot;https://hugehoo-blog.vercel.app/blog/DistributedSystems/Raft%20Consensus%20Algorithm&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hugehoo-blog.vercel.app/blog/DistributedSystems/Raft%20Consensus%20Algorithm&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1731974813643&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;https://hugehoo-blog.vercel.app/blog/DistributedSystems/Raft%20Consensus%20Algorithm&quot; data-og-description=&quot;Raft 논문을 선택한 이유 작년에 참여한 카프카 소모임에서 zookeeper 대신 kraft 가 등장할 것 이란 얘길 들은적이 있다. 카프카도 kraft도 모르던 시절이라 어떻게 주키퍼를 대체한다는 건지, 막연한 &quot; data-og-host=&quot;hugehoo-blog.vercel.app&quot; data-og-source-url=&quot;https://hugehoo-blog.vercel.app/blog/DistributedSystems/Raft%20Consensus%20Algorithm&quot; data-og-url=&quot;https://hugehoo-blog.vercel.app/blog/DistributedSystems/Raft%20Consensus%20Algorithm&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bNkTa4/hyXzM35rFx/MmUvMLJFaP8fkdVsKhPl31/img.png?width=1280&amp;amp;height=850&amp;amp;face=0_0_1280_850,https://scrap.kakaocdn.net/dn/BoBGl/hyXzLjNyhB/sLkttZKCJkVYQuoL4RWKcK/img.png?width=1082&amp;amp;height=902&amp;amp;face=0_0_1082_902,https://scrap.kakaocdn.net/dn/DFHng/hyXzIUVpOO/JKWOezp3IRgn7HxvkYyBtk/img.png?width=1020&amp;amp;height=791&amp;amp;face=0_0_1020_791&quot;&gt;&lt;a href=&quot;https://hugehoo-blog.vercel.app/blog/DistributedSystems/Raft%20Consensus%20Algorithm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hugehoo-blog.vercel.app/blog/DistributedSystems/Raft%20Consensus%20Algorithm&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bNkTa4/hyXzM35rFx/MmUvMLJFaP8fkdVsKhPl31/img.png?width=1280&amp;amp;height=850&amp;amp;face=0_0_1280_850,https://scrap.kakaocdn.net/dn/BoBGl/hyXzLjNyhB/sLkttZKCJkVYQuoL4RWKcK/img.png?width=1082&amp;amp;height=902&amp;amp;face=0_0_1082_902,https://scrap.kakaocdn.net/dn/DFHng/hyXzIUVpOO/JKWOezp3IRgn7HxvkYyBtk/img.png?width=1020&amp;amp;height=791&amp;amp;face=0_0_1020_791');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;https://hugehoo-blog.vercel.app/blog/DistributedSystems/Raft%20Consensus%20Algorithm&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Raft 논문을 선택한 이유 작년에 참여한 카프카 소모임에서 zookeeper 대신 kraft 가 등장할 것 이란 얘길 들은적이 있다. 카프카도 kraft도 모르던 시절이라 어떻게 주키퍼를 대체한다는 건지, 막연한&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hugehoo-blog.vercel.app&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;⬆️ 위 블로그에 내용을 보완하여 새로 작성했습니다. 해당 링크를 누르시면 더 가독성 좋은 아티클을 읽으실 수 있습니다  &lt;/b&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;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;br /&gt;Raft 논문을 선택한 이유&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;작년에 참여한 카프카 소모임에서 zookeeper 가 사라지고 kraft 가 그 자리를 대신할 것이라는 말을 들은적이 있다. 카프카도 kraft도 모르던 시절이라 kraft 가 뭐길래 이를 대체할 수 있는지 막연한 궁금증만 가졌다. 시간이 좀 더 흘러 분산 시스템을 공부하면서 raft 를 알게 됐고, 어렴풋이 기억하던 kraft 의 근간이 되는 이론이란 것도 알게 됐다. 뿐만 아니라 쿠버네티스 클러스터 구축에 필요한 etcd 의 근간 역시 raft 라는 걸 알게 돼면서, raft 를 제대로 배워보고자 논문을 선택하게 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;Raft 가 떠오를 수 밖에 없던 배경&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Raft&lt;/b&gt; 는 단순히 말하면 복제된 로그를 관리하기 위한 알고리즘이다. 복제된 로그는 분산시스템에서 각각의 개별 노드에 저장되는데, 이 때 로그를 안전하고 일관성 있는 방향으로 복제하기 위한 절차를 &lt;b&gt;합의 알고리즘&lt;/b&gt;이라 부른다. 즉 Raft 는 합의 알고리즘을 구현하기 위한 개념 정도로 이해할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mKrQV/btsKyIns8z9/yxqo8BDW5k4wl2Ctpz8UK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mKrQV/btsKyIns8z9/yxqo8BDW5k4wl2Ctpz8UK1/img.png&quot; data-alt=&quot;논문 제목&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mKrQV/btsKyIns8z9/yxqo8BDW5k4wl2Ctpz8UK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmKrQV%2FbtsKyIns8z9%2Fyxqo8BDW5k4wl2Ctpz8UK1%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;1272&quot; height=&quot;218&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;논문 제목&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;Raft 를 소개하는 논문은 제목부터 이해할 수 있는(Understandable) &lt;b&gt;합의 알고리즘&lt;/b&gt;에 포커스를 둔다. 이전의 합의 알고리즘은 어땠길래 Understandable 이란 워딩까지 사용하며 Raft 를 소개하는 걸까? Raft 이전의 널리 알려진 합의 알고리즘은 Lesile Lamport(83) 가 고안한 Paxos Protocol  으로, 논문에선 당시 스탠포드 대학의 학생들도 이를 이해하는데 1년이 넘는 시간이 걸렸다고 소개한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이러한 배경 때문에 Raft 는 처음부터 &quot;이해 가능한&quot; 이란 측면에서 중점을 두고 발전됐다. Paxos 만큼 효율적인 알고리즘이지만, Paxos 보다는 더 이해하기 쉬운 알고리즘이기도 한 셈이다.&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;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;Replicated State Machine, 복제 상태 머신이란?&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;1020&quot; data-origin-height=&quot;791&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXh8fk/btsKCmkcGKM/9hKMJMTM7GExpq88GyjQ60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXh8fk/btsKCmkcGKM/9hKMJMTM7GExpq88GyjQ60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXh8fk/btsKCmkcGKM/9hKMJMTM7GExpq88GyjQ60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXh8fk%2FbtsKCmkcGKM%2F9hKMJMTM7GExpq88GyjQ60%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;668&quot; height=&quot;518&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;791&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;합의 알고리즘&lt;/b&gt;은 복제 상태 머신 개념에서 출발한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;복제 상태 머신이란 무엇일까?&lt;/b&gt;&amp;nbsp; 복제 상태 머신(Replicated State Machine)은 분산 시스템에서 여러 노드가 동일한 상태를 유지하기 위해 사용 되는 개념으로, 분산된 노드들이 &lt;b&gt;같은 명령을 순서대로 실행&lt;/b&gt;하여 &lt;b&gt;동일한 상태&lt;/b&gt;를 보장한다. 여기서 언급된 &lt;b&gt;머신&lt;/b&gt;은 클러스터의 노드 중 하나, 즉 서버를 의미하며 `&lt;b&gt;같은 명령을 순서대로 실행&lt;/b&gt;`한다는 것은 특정 노드의 상태를 변경(x &amp;lt;- 5)하는 명령이 입력될 때, 나머지 다른 노드에서도 &lt;b&gt;명령이 동일하게 실행&lt;/b&gt;되는 것을 의미힌다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;이렇게 분산된 노드에서 상태를 동일하게 복제하면 클러스터의 &lt;b&gt;가용성&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt;(availability)&lt;/span&gt;&lt;/b&gt;이 보장된다. 즉 &lt;b&gt;복제 상태 머신&lt;/b&gt;은 분산 시스템에서의 다양한 &lt;b&gt;내결함성&lt;/b&gt; 문제를 해결하기 위해 사용될 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;그럼 복제 상태 머신은 분산 시스템의 모든 서버 상태를 어떻게 동일하게 할 수 있을까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;바로 &lt;b&gt;복제 로그&lt;/b&gt;를 구성해서 복제 상태 머신을 구현할 수 있다. 복제 로그의 목표는 클러스터에 연결된 모든 노드에 사용자의 명령어를 동일한 순서로 저장하는 것이다. 일관된 로그를 복제하면 각 노드의 상태 머신이 로그의 명령(command)을  순차적으로 실행하여 동일한 상태를 만들 수 있다. 각 상태머신은 결정론적(deterministic) 이므로 같은 상태를 계산하고, 같은 순서의 결과들을 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  &lt;b&gt;결정론적 (Deterministic)&lt;/b&gt; : 결정론적 알고리즘이란 특정 입력(input) 에 대해 특정 결과(output)를 반환하는 의미로, 여러 복제된 상태 머신들이 동일한 복제 로그를 토대로 명령을 실행하면, 분산 노드들은 결국 같은 결과를 반환할 수 있다.&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;복제 상태 머신&lt;/b&gt;은 다음의 속성을 만족해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;- &lt;b&gt;안정성&lt;/b&gt; : 네트워크 딜레이, 패킷 손실, 중복 전송 같은&amp;nbsp;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Non-Byzantine 조건에서 결함이 없어야 한다. &lt;/span&gt;(&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;Non-Byzantine 조건이란?)&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;Non-Byzantine 조건이란 장애가 발생할 수 있지만 시스템 구성 요소가 악의적이거나 임의의 행동을 하지 않는 환경을 의미한다. Non-Byzantine 환경의 복제 상태 머신 프로토콜에서는 다음을 가정한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;1. 충돌 회피 : 노드들은 서로 다른 결정이나 결과를 만들어내지 않는다. 장애가 발생하더라도 임의의 오류없이 동기화된 상태를 유지한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;2. 일관된 메시지 전달 : 네트워크 통신이 손실되거나 지연될 순 있지만, 메시지가 임의로 변조되진 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;3. 안정성과 라이브니스 보장 : 시스템의 모든 정당한 요청은 응답을 받으며, 시간이 지나면 각 노드가 동일한 상태를 복제한다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;- 가용성 : 클러스터의 서버 중 과반수 이상이 동작할 수 있고 서로 통신이 가능한 상태라면, 몇몇 서버에 대한 장애 허용성을 가진다. 다운된 서버는 일시적인 중단으로 여기며 나중에 복구된다. 예를 들어 클러스터의 5대 서버 중 2대 서버의 장애까지는 견뎌 낼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;- 일반적으로 클러스터의 과반수가 단일 RPC 호출에 응답하면 command 를 완료할 수 있다. (여기서 RPC 는 추후에 다루도록)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;- 로그 일관성을 유지하기 위해&lt;b&gt; 타이밍&lt;/b&gt;에&amp;nbsp;의존하지 않는다. 타이밍에 의존하면 왜 안될까? 분산 시스템에서 타임 스탬프를 이용해 최신 데이터를 판단하는 경우를 가정해보자.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;문제상황&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;1. &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt;고장난 클럭 케이스 :&lt;/span&gt; 노드 A 의 시간이 고장나서 1시간 뒤로 설정되면, 과거의 데이터라고 판단하여 최신성을 보장받지 못할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;2. 네트워크 지연 케이스 : 노드 A 의 변경사항이 네트워크 지연으로 인해 나중에 다른 노드에 도착하면, 다른 노드에선 A 의 과거 타임스탬프를 가진 데이터를 무시하거나 충돌 처리에 문제가 발생할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 같은 이유로 타이밍에 의존하면 정확한 로그 일관성을 유지하기 어려워, 최신 데이터를 식별하기 어렵게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;이해하기 쉬운 합의 알고리즘을 구성하는 요소&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;Raft 설계의 목표 중 한가지는 기존에 이해하기 어려웠던 Paxos 를 대체하는, 이해하기 쉬운 합의 알고리즘을 만드는 것이었다. 많은 개발자들이 알고리즘을 쉽게 이해함과 동시에 시스템을 구축 시 알고리즘에 대한 직관을 얻을 수 있어야 했다. Raft 설계자들은 2가지 접근으로 알고리즘을 설계했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;1. Problem Decomposition : &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR'; letter-spacing: 0px;&quot;&gt;Raft 는 '합의'라는&amp;nbsp; 복잡한 문제를 해결하기 위해 그보다 작은 문제로 쪼개서 해결하는 분할/정복을 이용한다. Raft 는 크게 &lt;b&gt;1. 리더 선출 2. 로그 복제 3. 안정성 보장&lt;/b&gt; 이라는 3개의 독립적인 문제로 나누어 하나의 합의 알고리즘을 만들기로 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR'; letter-spacing: 0px;&quot;&gt;&lt;b&gt;2. Simplify the State Space (상태 공간 단순화) :&lt;/b&gt; 시스템이 가질 수 있는 가능한 상태의 수를 줄여 시스템을 더 일관성 있게 만들고 비결정성을 가능한한 제거한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR'; letter-spacing: 0px;&quot;&gt;Raft 는 합의 라는 복잡한 문제를 해결하기 위해 더 작은 3개의 독립적인 하위 문제로 분리(분할) 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR'; letter-spacing: 0px;&quot;&gt;- 리더 선출 :기존 리더 노드에 장애가 발생하거나 실패할 때 새로운 리더를 선출한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR'; letter-spacing: 0px;&quot;&gt;- 로그 복제 : 오직 리더만 클라이언트가 요청하는 로그 생성을 허용하여 전체 클러스터에 걸쳐 로그를 복제할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR'; letter-spacing: 0px;&quot;&gt;- 안정성 : Raft 의 핵심적인 안정성은 &lt;b&gt;상태 머신&lt;/b&gt;으로, 만약 어떤 서버가 특정 로그 항목을 자신의 상태 머신에 적용(commit)했다면 다른 서버는 동일한 로그 인덱스에 다른 명령(command)을 적용할 수 없다.&amp;nbsp;&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;각각의 노드는 RPC(원격 프로시저 호출)로 서로 통신하며, 합의 알고리즘에서는 2종류의 RPC, RequestVote RPC 와 AppendEntries RPC 를 필요로 한다. RequestVote RPC 는 리더 선출 기간 중 후보자가 호출하는 RPC로 다른 팔로워 노드들에게 자신에게 투표하라는 메시지를 담아 보낸다. AppendEntries RPC 는 리더들이 로그를 다른 노드에 복제하거나, 하트비트 통신을 위해 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;Raft 에는 총 3가지의 상태 (Leader, Follower, Candidate) 가 존재하며, 각 노드는 한 순간에 하나의 상태만 가질 수 있다. 노드는 내부적으로 임기(term) 를 가지고 있어, 각 임기마다 하나의 리더를 선출하여 해당 임기의 대표성을 가진다. 임기는 매 임기마다 1씩 증가하는(auto-increment) 형태로 구분할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1505&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIGhsg/btsKCWS5gwD/zEWFaa9RNfO2KYD0ofS0k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIGhsg/btsKCWS5gwD/zEWFaa9RNfO2KYD0ofS0k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIGhsg/btsKCWS5gwD/zEWFaa9RNfO2KYD0ofS0k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIGhsg%2FbtsKCWS5gwD%2FzEWFaa9RNfO2KYD0ofS0k1%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;761&quot; height=&quot;506&quot; data-origin-width=&quot;1505&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;각 임기마다 노드가 가질 수 있는 상태는 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;- &lt;b&gt;리더, Leader :&lt;/b&gt; 클러스터는 하나의 리더와 나머지 팔로워로 구성된다. 오직 리더만이 클라이언트의 모든 요청을 수신하며 자신의 로컬에 로그를 적재한 후 팔로워에게 요청을 전달한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;- &lt;b&gt;팔로워, Follower :&lt;/b&gt; 팔로워는 클라이언트로부터 받은 요청을 리더에게 리다이렉트 하거나, 리더에게 전달받은 요청만 수신하여 처리하는 역할을 수행한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;- &lt;b&gt;후보자, Candidate :&lt;/b&gt; 새로운 리더를 선출하기 위해 후보가 된 상태이다. 리더 선출과정에서 여러 후보가 생길 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1740&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwRVKO/btsKDfx9wfk/KHTQo6pwXwGc34ML74h7qK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwRVKO/btsKDfx9wfk/KHTQo6pwXwGc34ML74h7qK/img.png&quot; data-alt=&quot;각 서버 노드마다 임기 전환은 다른 시간대에 포착될 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwRVKO/btsKDfx9wfk/KHTQo6pwXwGc34ML74h7qK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwRVKO%2FbtsKDfx9wfk%2FKHTQo6pwXwGc34ML74h7qK%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;722&quot; height=&quot;415&quot; data-origin-width=&quot;1740&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;각 서버 노드마다 임기 전환은 다른 시간대에 포착될 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;Raft 는 리더 선출을 위해 Heartbeat 메커니즘을 사용한다. 리더는 주기적으로 하트비트를 팔로워 노드에 보내 각 노드간 상태를 유지한다. (하트비트는 로그항목을 포함하지 않은 AppendEntries RPC 로 보낸다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터의 노드들은 최초로 부팅될 때 팔로워 상태로 시작한다. 이 때 각 노드마다 랜덤한 타임아웃(election timeout)이 설정돼 있는데, 이 타임아웃 내에 하트비트를 받지 못하면 살아있는 리더가 없다고 판단하여 새로운 리더를 선출하기 위한 선거를 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타임아웃을 먼저 초과한 노드(팔로워)는 선거를 시작하기 위해 자신의 현재 임기 상태를 증가시키고 후보자(Candidate) 상태로 전환한다. 해당 후보자는 스스로에게 투표한 후, 클러스터 내 노드들에 병렬적으로&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;RequestVote RPC를&lt;span&gt; 호출하여 자신을 리더로 투표해달라는 사인을 보낸다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선거에서 승리하면 (정족수 이상 투표) 해당 후보자는 리더로 전환된다. 하지만 &lt;b&gt;실패하는 경우&lt;/b&gt;도 발생하는데, &lt;b&gt;a) 다른 후보자가 먼저 과반수 투표를 받아 리더가 되는 경우&lt;/b&gt;나, 리더 선출에서 &lt;b&gt;b)&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 서버들은 동일 임기 내에 선착순으로 오직 하나의 투표에게만 투표한다. (1 노드 1 투표) 그리고 정족수 이상 득표한 후보자만이 리더로 선출되는 규칙은 적어도 하나의 후보자만 특정 임기 내에서 승리할 수 있음을 보장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;랜덤 타임아웃&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;Raft에선 여러 후보자가 발생한 후 투표가 동률로 끝나는 상황을 Split Vote 라 한다. 이 경우 투표는 결렬되고 재투표를 진행해야한다. 이런 케이스 발생을 줄이기 위해 각 서버는 랜덤으로 타임아웃 시간이 설정된다. 특정 후보자가 과반수 표를 얻지 못해 선거가 실패하면, 일정 시간 후 다시 (랜덤한) 타임아웃이 발생하고 새로운 선거가 시작된다. 랜덤 타임아웃 덕분에 선거가 여러 번 반복될수록 동시에 &lt;span style=&quot;text-align: start;&quot;&gt;후보가&lt;/span&gt; 출마할 확률이 줄어, 결국 한 후보자가 과반수 표를 얻어 리더로 선출될 가능성이 높아진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;로그 복제&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;Raft 의 로그는 &lt;b&gt;index(위치 값)&lt;/b&gt;, &lt;b&gt;임기 번호&lt;/b&gt;, &lt;b&gt;상태 변경 명령(command)&lt;/b&gt; 3가지로 구성된 엔트리의 집합체를 의미한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;Raft 는 어떻게 로그 일관성을 유지하여 복제 상태 머신을 구성할까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;먼저 클라이언트가 새로운 요청을 리더에게 전송하면, 리더는 이 요청(command)을 자신의 로그에 추가(append)한다. 이후 리더는 AppendEntries RPC 를 모든 노드에 병렬적으로 요청해 해당 항목을 복제하도록 한다. 그 후 과반수 합의 과정을 거치는데,&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt; 리더는 클러스터 과반수(정족수)에서 로그가 복제된 것을 확인하면, 자산의 로그 엔트리를 커밋하고 팔로워에게도 변경 사항이 커밋됐음을 보낸다. 그 후 팔로워도 자신의 로그 엔트리를 커밋한다(그전까진 복제만 해둠)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;1. &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt;먼저 클라이언트가 새로운 요청을 리더에게 전송하면, &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt;리더는 이 요청(command)을 자신의 로그에 추가(append)한다.&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&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/bK6EX5/btsKDc2Al6I/K1nzWCtnpdLHljFA81BPkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK6EX5/btsKDc2Al6I/K1nzWCtnpdLHljFA81BPkk/img.png&quot; data-origin-width=&quot;1703&quot; data-origin-height=&quot;999&quot; data-is-animation=&quot;false&quot; width=&quot;582&quot; height=&quot;341&quot; style=&quot;width: 44.5855%; margin-right: 10px;&quot; data-widthpercent=&quot;45.11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK6EX5/btsKDc2Al6I/K1nzWCtnpdLHljFA81BPkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK6EX5%2FbtsKDc2Al6I%2FK1nzWCtnpdLHljFA81BPkk%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;1703&quot; height=&quot;999&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pgUw7/btsKDZnTqpv/h5hPbV7Gg1ficwHzUw5V20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pgUw7/btsKDZnTqpv/h5hPbV7Gg1ficwHzUw5V20/img.png&quot; data-origin-width=&quot;363&quot; data-origin-height=&quot;175&quot; data-is-animation=&quot;false&quot; style=&quot;width: 54.2517%;&quot; data-widthpercent=&quot;54.89&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pgUw7/btsKDZnTqpv/h5hPbV7Gg1ficwHzUw5V20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpgUw7%2FbtsKDZnTqpv%2Fh5hPbV7Gg1ficwHzUw5V20%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;363&quot; height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;2. &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt;이후 리더는 AppendEntries RPC 를 모든 노드에 병렬적으로 요청해 해당 항목을 복제한다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&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/cHGLcr/btsKDzJJt1S/xZJnH1QftvCeTKk8B0JFFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHGLcr/btsKDzJJt1S/xZJnH1QftvCeTKk8B0JFFk/img.png&quot; data-origin-width=&quot;1539&quot; data-origin-height=&quot;1000&quot; data-is-animation=&quot;false&quot; style=&quot;width: 42.0975%; margin-right: 10px;&quot; data-widthpercent=&quot;42.59&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHGLcr/btsKDzJJt1S/xZJnH1QftvCeTKk8B0JFFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHGLcr%2FbtsKDzJJt1S%2FxZJnH1QftvCeTKk8B0JFFk%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;1539&quot; height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5Poo9/btsKDqzz4g1/cYhESHZJfB7dqK36BNj8UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5Poo9/btsKDqzz4g1/cYhESHZJfB7dqK36BNj8UK/img.png&quot; data-origin-width=&quot;363&quot; data-origin-height=&quot;175&quot; data-is-animation=&quot;false&quot; style=&quot;width: 56.7397%;&quot; data-widthpercent=&quot;57.41&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5Poo9/btsKDqzz4g1/cYhESHZJfB7dqK36BNj8UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5Poo9%2FbtsKDqzz4g1%2FcYhESHZJfB7dqK36BNj8UK%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;363&quot; height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;3. &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;그 후 과반수 합의 과정을 거치는데,&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;리더는 클러스터 과반수(정족수)에서 로그가 복제된 것을 확인하면, 자산의 로그 엔트리를 커밋하고 팔로워에게도 변경 사항이 커밋됐음을 보낸다. 아래 이미지에서 정족수는 2 (= n/2 + 1)이므로 Node2 혹은 Node3 중 하나에만 복제가 성공하면 해당 로그를 커밋할 수 있다.&lt;/span&gt;&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/l8QPx/btsKBTprn38/Vb0M3MMKa4AJXXhzm6unck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l8QPx/btsKBTprn38/Vb0M3MMKa4AJXXhzm6unck/img.png&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;1000&quot; data-is-animation=&quot;false&quot; style=&quot;width: 43.201%; margin-right: 10px;&quot; data-widthpercent=&quot;43.71&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l8QPx/btsKBTprn38/Vb0M3MMKa4AJXXhzm6unck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl8QPx%2FbtsKBTprn38%2FVb0M3MMKa4AJXXhzm6unck%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;1622&quot; height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JayB4/btsKCfTo6DF/qSZkyTz5hk1uxETwsjgvFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JayB4/btsKCfTo6DF/qSZkyTz5hk1uxETwsjgvFK/img.png&quot; data-origin-width=&quot;376&quot; data-origin-height=&quot;180&quot; data-is-animation=&quot;false&quot; style=&quot;width: 55.6363%;&quot; data-widthpercent=&quot;56.29&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JayB4/btsKCfTo6DF/qSZkyTz5hk1uxETwsjgvFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJayB4%2FbtsKCfTo6DF%2FqSZkyTz5hk1uxETwsjgvFK%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;376&quot; height=&quot;180&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;리더가 전송하는 AppendEntries RPC 는 아래 요소들로 이뤄져있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1731156910990&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;term: 현재 leader의 임기
leaderId : leader id
prevLogIndex : 바로 이전의 log index
prevLogTerm : 바로 이전의 log index에 기록된 임기(term)
entries[] : 저장할 로그 엔트리 (비어있으면 heartbeat용)
leaderCommit : 현재 leader의 commitIndex&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AppendEntries&lt;/b&gt; 상세 동작 순서를 살펴보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Follower 의 현재 임기(currentTerm) 보다 리더로 부터 전달받은 term 이 낮으면 (term &amp;lt; currentTerm) &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;return false&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 전달받은 prevLogIndex 에 해당하는 엔트리가 없으면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;return false&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 리더의 index 는 100 인데 복사하려는 follower 의 index 가 98인, prevLogIndex(=99)에 해당하는 entry 가 없는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 이미 존재하는 엔트리가 새로운 엔트리와 충돌이 발생한 경우(=같은 index 지만 다른 term 일 경우) -&amp;gt; 기존 엔트리 삭제 후 새로운 엔트리 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 새로 추가된 엔트리의 index 와 리더의 commitIndex 중 더 작은 값을 follwer 의 commitIndex 에 set&lt;/p&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;글이 생각보다 길어져 2편에서 이어지도록 하겠습니다. 2편에서는 로그의 일관성을 지키기 위해 Raft 프로토콜에서 어떤 식으로 복제가 진행되는지 원리를 자세히 다뤄보겠습니다.&lt;/p&gt;</description>
      <author>Hugehoo</author>
      <guid isPermaLink="true">https://junior-datalist.tistory.com/398</guid>
      <comments>https://junior-datalist.tistory.com/398#entry398comment</comments>
      <pubDate>Sat, 9 Nov 2024 23:39:33 +0900</pubDate>
    </item>
    <item>
      <title>테크 블로그 모아보기 개발 기록 (2) : Go 언어와 서버리스 프레임워크</title>
      <link>https://junior-datalist.tistory.com/397</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1283&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rrePG/btsKk53Dw82/xpKbGNZW2wrIKtIr1gZ0rK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rrePG/btsKk53Dw82/xpKbGNZW2wrIKtIr1gZ0rK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rrePG/btsKk53Dw82/xpKbGNZW2wrIKtIr1gZ0rK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrrePG%2FbtsKk53Dw82%2FxpKbGNZW2wrIKtIr1gZ0rK%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;759&quot; height=&quot;460&quot; data-origin-width=&quot;1283&quot; data-origin-height=&quot;778&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic'; color: #333333;&quot;&gt;&lt;b&gt;서론&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333;&quot;&gt;이 글은 Golang 어플리케이션을 Serverless framework 를 이용해 AWS Lambda 에 배포하는 과정을 다룬다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333;&quot;&gt;현재 운영중인 사이드 프로젝트(&lt;i&gt;&lt;a style=&quot;color: #333333;&quot; href=&quot;https://blog-scrapper-ui.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;  테크 블로그 모아보기&lt;/span&gt;&lt;/a&gt;&lt;/i&gt;) 는 Go 언어를 기반으로 AWS Lambda(이하 람다) 에 배포되어 있다. 처음 람다를 배울 땐 AWS Console 에서 일일이 API Gateway 를 설정하고 Lambda 함수의 코드를 직접 작성한 기억이 나는데, 당시만 해도 간단한 파이썬 함수를 호출하는 정도라 AWS Console 에 코드를 복붙하는 정도로 람다를 맛보기 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333;&quot;&gt;하지만 실제로 운영할 애플리케이션 코드는 단순 복사 붙여넣기로 람다에 배포할 수 없었다. 파일 수도 많을 뿐더러 매번 배포할 때 마다 그런 번거로운 일을 할수도 없기 때문이다. 그리하여 람다의 배포 자동화는 어떻게 할 수 있을지를 고민하다 AWS CLI, AWS SAM, Serverless Framework 등 다양한 툴을 알게 됐다. 결론적으로 필자는 Serverless Framework (이하 서버리스 프레임워크) 를 선택했고 간단한 커맨드 입력만으로 업데이트가 있을 때마다 간단히 배포를 진행할 수 있게 됐다.&lt;br /&gt;이 글에선 서버리스 프레임워크란 무엇인지, Go/Gin 웹 프레임워크로 개발된 애플리케이션을 배포하는 개략적인 방법을 알아보겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Go 와 AWS Lambda 의 조합&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333;&quot;&gt;go 언어 특성 상 람다로 배포하면 제격이라고 느낀 포인트가 몇가지 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;우선 빠른 초기 실행 시간 (Cold start time)&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333;&quot;&gt;Go 는 컴파일 언어로 &lt;b&gt;바이너리 파일&lt;/b&gt;로 변환되어 람다에 제공된다. 이는 런타임 시 별도의 인터프리터가 필요 없어 Python 이나 Node.js 같은 인터프리터 언어보다 람다에서 빠르게 실행될 수 있다. 쉽게 말해 실행할 준비가 완료된 상태로 제공되기 때문에 성능면에서 효율적이라 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;람다는 함수가 호출될 때 &lt;b&gt;서버 인스턴스를 즉시 준비&lt;/b&gt;하여 실행하는데, 이 과정에서 람다는 새롭게 생성된 인스턴스에서 코드의 초기화를 진행한다. 이렇게 인스턴스가 새로 시작되는 것을 콜드 스타트(Cold start) 라고 한다. &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이러한 콜드 스타트는 함수가 오랜 시간 동안 호출되지 않았거나 새로운 인스턴스가 생성되는 경우에 발생하며, 이 초기화 과정에서 응답 지연이 발생할 수 있다. 여기서 Go의 컴파일 언어라는 특징은 별도의 런타임을 요구하지않고 곧바로 실행할 수 있기 때문에 콜드 스타트 시간을 줄일 수 있다. 또한 Go 바이너리는 일반적으로 경량이고 메모리 사용량에 최적화 되어 있어, 초기화 시 메모리 사용량이 적어 콜드 스타트의 영향을 줄이는데 도움이 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;작은 바이너리 파일 크기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다른 컴파일 언어인 Java 를 생각해보자. Java 는 컴파일 된 후 바이트 코드로 변환된다. Java 개발자라면 잘 알다시피 이 바이트 코드를 실행하려면 JVM 이라는 가상 머신을 필요로 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;반면 Go&amp;nbsp; 는 빌드 시 &lt;b&gt;네이티브 바이너리 파일&lt;/b&gt;을 생성한다. &lt;b&gt;네이티브&lt;/b&gt;&amp;nbsp;&lt;b&gt;바이너리 파일&lt;/b&gt;이란 특정 운영체제와 CPU 에 맞게 컴파일된 실행 파일을 의미한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm3g4g/btsKf6HSjY2/yHKl0uD6hsMjUt1fJcdaH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm3g4g/btsKf6HSjY2/yHKl0uD6hsMjUt1fJcdaH0/img.png&quot; data-alt=&quot;서버리스 프레임워크에서 Go를 빌드하는 커맨드. 빌드 시 생성될 바이너리 파일은 Linux 운영체제에서 실행하겠다는 의미다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm3g4g/btsKf6HSjY2/yHKl0uD6hsMjUt1fJcdaH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm3g4g%2FbtsKf6HSjY2%2FyHKl0uD6hsMjUt1fJcdaH0%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;1168&quot; height=&quot;200&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;서버리스 프레임워크에서 Go를 빌드하는 커맨드. 빌드 시 생성될 바이너리 파일은 Linux 운영체제에서 실행하겠다는 의미다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이 바이너리 파일은 시스템에서 바로 실행될 수 있어 별도의 추가 소프트웨어나 런타임 환경을 필요로 하지 않는다. 즉 Go 코드는 컴파일 되면 자체적으로 실행 가능한 독립형 바이너리를 생성하고, 이를 통해 AWS Lambda 에서 별도의 의존성 설치 없이 간단히 배포할 수 있게 된다. 또한 바이너리 파일의 크기가 작기 때문에 Lambda 의 배포 패키지 크기가 작아져 업로드 및 배포 속도에도 효율적인 영향을 줄 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Go 와 Lambda&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Go 애플리케이션을 위한 웹 프레임워크에는 여러 종류(Gin, Fiber, Echo etc)가 있고 필자는 Gin 을 선택했다. Gin 은 경량 웹 프레임워크로 람다에서 실행 시 Cold start 가 발생할 때도 비교적 빠르게 실행할 수 있단 장점이 있어 선택했다. 무엇보다 참고할 자료가 가장 풍부한 점도 선택에 한 몫했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1729870196946&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import (
    &quot;github.com/aws/aws-lambda-go/events&quot;
    &quot;github.com/aws/aws-lambda-go/lambda&quot;
    &quot;github.com/awslabs/aws-lambda-go-api-proxy/gin&quot;
    &quot;log&quot;
)

var ginLambda *ginadapter.GinLambda

func main() {
    log.Println(&quot;  Start Lambda&quot;)
    lambda.Start(Handler)
}

func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    return ginLambda.ProxyWithContext(ctx, request)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;main 함수&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;lambda.Start 함수는 람다가 시작되면 Handler 함수가 호출되도록 설정한다. Handler 는 람다에 의해 트리거되는 이벤트를 핸들링할 함수로, 쉽게 말해 람다가 실행될 때 호출되는 함수라고 보면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Handler 함수&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;code&gt;ctx&lt;/code&gt; : 람다가 제공하는 context.Context 객체로, 람다 실행에 관련된 타임아웃이나 종료 신호 등 여러 메타데이터를 담고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;code&gt;request&lt;/code&gt; : &lt;code&gt;events.APIGatewayProxyRequest&lt;/code&gt; 타입으로 API Gateway 를 통해 들어온 HTTP 요청을 나타낸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;code&gt;ginLambda.ProxyWithContext(ctx, request)&lt;/code&gt; : ginLambda 는 람다와 연동을 돕는 헬퍼 객체로, &lt;code&gt;ProxyWithContext&lt;/code&gt; 메서드를 사용해 request 객체를 gin 프레임워크에서 처리할 수 있는 형식으로 request 를 변환하고, response 결과도 반환해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Gin 웹프레임워크를 람다에서 사용할 수 있도록 기본적인 세팅을 완료했으니, ping 을 날려 통신할 수 있도록 라우터를 설정해보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1729871132547&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func init() {
    log.Println(&quot;✅ Starting initialization...&quot;)
    gin.SetMode(gin.ReleaseMode)

    config := cors.DefaultConfig()
    config.AllowOrigins = []string{&quot;*&quot;} // Allow all origins
    config.AllowMethods = []string{&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;PATCH&quot;, &quot;DELETE&quot;, &quot;HEAD&quot;, &quot;OPTIONS&quot;}
    config.AllowHeaders = []string{&quot;Origin&quot;, &quot;Content-Length&quot;, &quot;Content-Type&quot;, &quot;Authorization&quot;}
    config.ExposeHeaders = []string{&quot;Content-Length&quot;}
    config.AllowCredentials = true
    config.MaxAge = 12 * time.Hour
    r.Use(cors.New(config))


    r := gin.Default()
    r.GET(&quot;/ping&quot;, func(c *gin.Context) {
        c.JSON(200, gin.H{
            &quot;message&quot;: &quot;pong&quot;,
        })
    })

    ginLambda = ginadapter.New(r)
    log.Println(&quot;✅ Initialization complete&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;init 함수&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Go 에서 init 함수는 패키지가 로드될 때 자동으로 호출되는 특별한 함수로, 명시적으로 호출하지 않아도 패키지가 처음으로 로드되는 시점에 자동으로 호출된다. 즉 main 함수보다 먼저 호출된다고 볼 수 있다. &lt;b&gt;init 이 호출되면서 Gin 서버 설정과 라우터 등록, 의존성 초기화 등의 작업을 설정한 후 main 함수로 넘어가게 된다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;code&gt;gin.SetMode(gin.ReleaseMode)&lt;/code&gt; : Gin을 &lt;b&gt;Release 모드&lt;/b&gt;로 설정한다. 이는 로그를 최소화하고 성능을 높이는 설정으로, AWS 람다처럼 리소스 제약이 있는 환경에서 효과적이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;code&gt;gin.Default()&lt;/code&gt; : Gin 라우터 객체를 기본 설정으로 생성한다.&amp;nbsp; Default 함수는 로깅과 리커버리 미들웨어를 기본적으로 포함한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;code&gt;ginadapter.New(r)&lt;/code&gt; : Gin 라우터를 람다에서 사용할 수 있는 핸들러로 변환하는 함수로, gin.Default() 가 할당된 r 변수를 인자로 넘겨 GinLambda 객체를 생성한다. Gin 으로 등록한 여러 라우터를 AWS 람다에서 사용할 수 있도록 변환하는 함수라 생각할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kRsJc/btsKjZQVtDA/Za0cHk2dL8N3vZvMB5MQ1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kRsJc/btsKjZQVtDA/Za0cHk2dL8N3vZvMB5MQ1k/img.png&quot; data-alt=&quot;aws-lambda-go-api-proxy 라이브러리의 gin/adapter.go  의 New()&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kRsJc/btsKjZQVtDA/Za0cHk2dL8N3vZvMB5MQ1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkRsJc%2FbtsKjZQVtDA%2FZa0cHk2dL8N3vZvMB5MQ1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;788&quot; height=&quot;169&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;aws-lambda-go-api-proxy 라이브러리의 gin/adapter.go  의 New()&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Serverless Framework 로 배포&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;501&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8G2tR/btsKeI2japq/JAUIGRDcRRJgFHAXcsYEh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8G2tR/btsKeI2japq/JAUIGRDcRRJgFHAXcsYEh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8G2tR/btsKeI2japq/JAUIGRDcRRJgFHAXcsYEh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8G2tR%2FbtsKeI2japq%2FJAUIGRDcRRJgFHAXcsYEh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;501&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;501&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;람다로 어플리케이션을 배포한 경험은 많지 않지만, &lt;a href=&quot;https://www.serverless.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;  &lt;b&gt;Serverless Framework&lt;/b&gt;&lt;/a&gt;를  사용하면  간단히 배포할 수 있다는 것을 익히 들어온터라 이번 기회에 사용해보기로 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333;&quot;&gt;서버리스 애플리케이션의 배포 자동화 선택지로는 &lt;b&gt;AWS CLI&lt;/b&gt; 나 &lt;b&gt;AWS SAM(Serverless Application Model)&lt;/b&gt; 을 활용하는 방법도 있지만, 최종적으로 서버리스 프레임워크를 선택했다. AWS SAM 도 비슷한 자동화 기능을 제공하지만 서버리스 프레임워크와 비교하면 초기 배포과정의 설정과 더 많은 선언적 방식이 필요해서 배제했다. AWS CLI 도 훌륭한 선택이지만 별도의 설정 파일 없이 명령어 기반으로 동작하기에 반복적인 작업에 비효율적일 것이라 판단했다.(사실 명령어 반복이야 shell script 로 뚝딱뚝딱 하면 될것 같기도..?)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333;&quot;&gt;결정적으로 Serverless Framework 는 각 언어에 맞는 프로젝트 템플릿이 잘 제공되어 있어 &lt;b&gt;AWS CLI&lt;/b&gt; 보다 나은 선택이라 판단했다. 위에서 언급한 이미지는 serverless.yml 의 일부인데 Go 애플리케이션을 빌드하기 위한 Go 빌드 명령어도 기본 템플릿에 포함할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333;&quot;&gt;서버리스 프레임워크는 어떤 식으로 작동하길래 람다의 배포를 편리하게 도와주는걸까?&lt;br /&gt;결론부터 말하면 서버리스 프레임워크는 AWS 의 Cloudformation 이라는 서비스를 이용한다. AWS 내에서 필요한 서비스를 JSON/YAML 파일로 형식에 맞춰 전달하면 필요한 서비스를 자동으로 생성/삭제 해주는 서비스인 셈이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;아래는 필자가 서버리스 프레임워크로 애플리케이션으로 배포하기 위해 필요한 yml 파일의 전문이다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;serverless.yml&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1729602786199&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;service: serverless-go

provider:
  name: aws
  runtime: provided.al2
  region: ap-northeast-2
  stage: ${opt:stage, 'dev'}

functions:
  api:
    handler: bootstrap
    timeout: 30 # Increase overall function timeout
    events:
      - http:
          path: /{proxy+}
          method: ANY
    environment:
      GO_LOG: info


package:
  patterns:
    - '!./**'
    - './bootstrap'

custom:
  golang:
    cmd: GOOS=linux GOARCH=amd64 go build -o bootstrap main.go&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;serverless.yml 파일과 더불어 해당 파일을 실행하는 스크립트를 자동화한 &lt;b&gt;Makefile&lt;/b&gt; 도 작성해주자. 이를 활용하면 매번 빌드 및 배포 명령어를 일일이 입력할 필요없이 단순히 &lt;code&gt;make deploy&lt;/code&gt; 명령만으로 Go 애플리케이션 빌드부터 Serverless Framework 로 Lambda 에 배포까지 한큐에 진행할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Makefile&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1729602958626&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;build:
	GOOS=linux GOARCH=amd64 go build -o bootstrap main.go
	chmod +x bootstrap

deploy: build
	serverless deploy --stage prod

clean:
	rm -f bootstrap&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이렇게 배포까지 진행하면 AWS Console 에서 서비스가 배포된 것을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다만 주의할 점은 해당 애플리케이션에서 외부 connection 을 맺는 과정이 제대로 이루어지지 않으면 배포에 실패한다(당연한 소리긴 함). 그래서 배포된 람다 함수가 외부 connection 과 잘 연결되는지, 포트 개방은 되어 있는지 잘 확인해봐야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQTuz8/btsKfLKKOEs/MK3EpOIK0k4x3dHrjpUPO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQTuz8/btsKfLKKOEs/MK3EpOIK0k4x3dHrjpUPO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQTuz8/btsKfLKKOEs/MK3EpOIK0k4x3dHrjpUPO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQTuz8%2FbtsKfLKKOEs%2FMK3EpOIK0k4x3dHrjpUPO1%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;733&quot; height=&quot;511&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;688&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;결론&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;서버리스 프레임워크를 활용하여 Go 애플리케이션을 람다에 배포하는 과정을 정리했다. 생각보다 글이 짧게 끝났는데 그만큼 서버리스 프레임워크를 사용하면 간단하게 Go 어플리케이션을 배포할 수 있다. 배포 이외에도 서버리스 프레임워크를 효율적으로 사용할 수 있는 방법은 무엇이 있을지 좀 더 고민이 필요한 것 같다. 현재는 단순 배포 용도로 사용중인데 이 과정을 자동화하는, 예를 들어 Github ACtions 와 연동하여 원격 레포지토리에 푸시하거나 main 브랜치에 머지할 때마다 배포가 되도록 개선해 볼 수 있겠다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Backend/  Go</category>
      <author>Hugehoo</author>
      <guid isPermaLink="true">https://junior-datalist.tistory.com/397</guid>
      <comments>https://junior-datalist.tistory.com/397#entry397comment</comments>
      <pubDate>Tue, 22 Oct 2024 23:00:27 +0900</pubDate>
    </item>
    <item>
      <title>테크 블로그 모아보기 개발 기록 (1)</title>
      <link>https://junior-datalist.tistory.com/396</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #ffd6d6;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;서론&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;글또 4년 차면 글을 쓰는 것뿐만 아니라 좋은 글을 찾아 읽는 것도 즐기게 된다. 여러 회사의 테크 블로그를 자주 챙겨 읽는데 매번 업데이트가 되는지, 안되는지 알 수 없으니 직접 페이지를 방문하는 수밖에 없었다. 카카오페이 같은 경우는 감사하게도 새로운 글이 올라올 때마다 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;링크드인에 &lt;/span&gt;노티를 주지만 그렇다고 모든 회사를 팔로우 할 순 없는 노릇이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;해결해야 할 문제가 얼마나 고통스러운지, 이걸 해결하면 얼마나 많은 사람들이 편안해지는 지에 따라 문제의 중요성을 파악하곤 하는데, 냉정하게 말해서 직접 블로그를 방문하여 새로운 글을 확인하는 게 그리 고통스러운 일은 아니다. 단지 조금 귀찮았을 뿐.. 그래서 나도 아주 약간의 노력만 들여 이 문제를 조금 편하게 만들어보자는 마음에   &lt;span style=&quot;color: #ffffff; background-color: #0593d3;&quot;&gt;&lt;a style=&quot;color: #ffffff; background-color: #0593d3;&quot; href=&quot;https://blog-scrapper-ui.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;테크 블로그 모아보기&lt;/b&gt;&lt;/a&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;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;span style=&quot;color: #ef5369; font-family: 'Nanum Gothic'; background-color: #ffd6d6;&quot;&gt;&lt;b&gt;백엔드&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;서버는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Go&lt;/b&gt;&lt;/span&gt; 와 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Gin&lt;/b&gt; &lt;b&gt;프레임워크&lt;/b&gt;&lt;/span&gt;로 개발했다. Go를 이용한 웹 스크래퍼 만드는 강의를 잠깐 들었는데, 강의에선 구직 시장 페이지를 대상으로 한 웹 스크래핑을 보여줬다. 내가 아는 스크래핑은 파이썬 selenium을 사용하는 게 전부였는데, Go와 Goquery를 이용한 스크래핑은 파이썬 대비 비교도 안될 만큼 놀라운 성능을 보여줬다. 이 점이 Go로 웹 스크래퍼를 만들어봐야겠다는 첫 동기부여가 됐다. (파이썬 비하 의도는 전혀 없다.. 아이 러브 파이썬!)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;실제로 스크래핑할 블로그가 늘어나면서 동기식, 순차적으로 블로그를 차례로 스크래핑하는 API는 시간이 상당히 오래 걸렸다. 하나의 블로그를 스크래핑할 때도 페이지의 수만큼 호출해야 하는 API 가 늘어나기 때문에 이를 동기식으로 호출하는 건 개선이 필요하다고 생각했다. 무엇보다 내가 사용하는 언어가 무엇인가, 무려 Golang 아니겠는가. 빠르다고 소문난 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Goroutine&lt;/b&gt;&lt;/span&gt;의 성능을 실제로 경험해 볼 수 있는 기회라 생각했고, 동기식으로 호출하던 모든 API를 고루틴에 태워버려 비약적인 속도 개선을 이룰 수 있었다. 이에 대한 내용은 &lt;a href=&quot;https://junior-datalist.tistory.com/395&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;별도 블로그에 첨부&lt;/a&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wht5Q/btsJQcWUATX/UPa4RKPQPtbeXQ2fM6vHnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wht5Q/btsJQcWUATX/UPa4RKPQPtbeXQ2fM6vHnK/img.png&quot; data-alt=&quot;고루틴에 태워버려잇&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wht5Q/btsJQcWUATX/UPa4RKPQPtbeXQ2fM6vHnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwht5Q%2FbtsJQcWUATX%2FUPa4RKPQPtbeXQ2fM6vHnK%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;657&quot; height=&quot;637&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;고루틴에 태워버려잇&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;스크래핑 코드를 짜는게 살짝 귀찮긴 했다. 수집할 블로그가 늘어나면서 스크래퍼 패키지를 회사마다 만들 수 밖에 없었는데, 오히려 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;medium&lt;/b&gt;&lt;/span&gt; 처럼 공통된 플랫폼을 사용하는 회사의 블로그는 공통된 html 구조를 가지고 있어 수월하게 스크래핑 할 수 있었다. (샤라웃 투 당근, 무신사, 29cm 레쓰고)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;195&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dZxFWj/btsJQjBAgRC/HFRDqVD3vwKd1DwGafRW41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dZxFWj/btsJQjBAgRC/HFRDqVD3vwKd1DwGafRW41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dZxFWj/btsJQjBAgRC/HFRDqVD3vwKd1DwGafRW41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdZxFWj%2FbtsJQjBAgRC%2FHFRDqVD3vwKd1DwGafRW41%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;195&quot; height=&quot;312&quot; data-origin-width=&quot;195&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;현재는 12개 정도의 회사를 스크래핑하기에, 12개의 패키지를 만들었다. 회사마다 html 구조가 다르니 어쩔 수 없다고 생각하는데 이를 기술적으로 풀 수 있는 방법은 없을지 더 고민해봐야겠다. 만약 스크래핑할 회사가 50개 정도로 늘어나면..? 50개의 서로 다른 패키지를 관리하는게 더 힘들 것 같다... RSS 라는 것을 사용하면 HTML 구조에 상관없이 업데이트가 가능하다고 하는데, RSS 를 적용할 수 있을지 좀 더 조사가 필요할 것 같다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Nanum Gothic'; background-color: #ffd6d6;&quot;&gt;&lt;b&gt;DB 선정&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;우선 여러 테크 블로그의 HTML 형식에 맞는 스크래퍼를 만들고, 스크래핑한 결과를 구조체에 담는다 (구조체는 Java 의 DTO, DAO 정도로 생각할 수 있다.) 구조체를 배열에 담고 이를 DB에 저장하는 아주 단순한 흐름이다. DB를 선정하는 과정에서 고려한 점은 스키마의 유연성인데, 아무래도 각 회사마다 보여줄 수 있는 데이터가 다르다고 생각하여 RDB 보단 Nosql 쪽으로 방향이 기울었다. 무엇보다 점진적으로 검색 기능도 추가하고 싶었는데, mysql의 전문검색 보단 mongoDB의 text 검색이나 elasticSearch 같은 검색 엔진이 적합할 것이라 생각해서 우선 mongoDB 를 선택했다. 그리하여 현재는 블로그 제목 필드에만 인덱싱을 걸어 빠른 속도로 검색이 가능하게 했다. 다음 단계에서는 블로그 전문을 인덱싱하여 검색할 수 있도록 만들 예정인데, 저장하는 블로그의 수가 점차 늘어나다 보니 mongoDB의 검색 성능으로는 한계가 있을 거란 우려가 생겨 elasticSearch 도입도 고려 중이다.&lt;/span&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Nanum Gothic'; background-color: #ffd6d6;&quot;&gt;&lt;b&gt;프론트엔드&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Typescript + React + Next.js로 앞단을 구축하고 Claude의 도움을 받아 UI를 어떻게든 그렸다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프론트엔드 개발은 첫 직장에서 리액트를 사용해본게 전부였고, 그 사이엔 프론트엔드 개발의 트렌드는 CRA에서 Next.js로 많이 넘어왔다고 느꼈다. 강의를 통해 Next.js를 찍먹할 수 있었는데 서버 사이드 렌더링 방식이 초기 로딩 속도를 개선하면서 사용성이 나아진걸 체감할 수 있었고,&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;SEO 측면이나 API 호출 성능 개선면에서도 만족스러웠기 때문에 이번 프로젝트에서도 Next.js 를 사용하기로 했다. &lt;/span&gt;vercel을 이용해서 serverless 배포를 무료로 할 수 있다는 점도 선택한 이유 중 하나였다. 초기 버전의 UI는 그저 블로그를 리스팅 하는데 그쳤는데, 검색 기능도 추가되면 좋겠다는 생각에 search input 컴포넌트를 추가했다. 당연히 서버도 mongoDB 에서 Title을 쿼리 할 수 있도록 개선했다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Nanum Gothic'; background-color: #ffd6d6;&quot;&gt;&lt;b&gt;디자인&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;처음 설계한 디자인은 너무 구렸다. (구려서 스크린샷도 안찍은듯 ㅇㅅㅇ) 이건 나도 안 쓸 것 같단 생각에 다른 기술 블로그를 레퍼런스 삼아 많이 찾아다녔다. 그중 오늘의 집 테크 블로그의 톤 앤 매너가 마음에 들었고 이를 기반으로 UI를 재편했다. 이 역시  Claude의 도움을 받았는데 오늘의집 기술 블로그 화면을 스크린샷 떠서 Claude 에 붙여넣고 이를 기반으로 HTML 과 CSS 를 next.js 에서 활용할 수 있는 형태로 만들어달라 했다. 그 이후 부턴 복붙과 짜집기의 연속이었고 나름 만족할 만한 깔끔한 UI 를 얻을 수 있었다. 사실 진짜 디자이너 분들의 시선에선  다는 말이 나올 수 있겠지만,,, 그렇지만 난 귀여운 백엔드 개발자인걸?! 디자인은 딱 Claude 수준의 결과물만 차용하는데 만족했다.&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/WTPjG/btsJQoCLK1m/u3lXCZOxtWkMTwpvKPZIm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WTPjG/btsJQoCLK1m/u3lXCZOxtWkMTwpvKPZIm1/img.png&quot; style=&quot;width: 59.7713%; margin-right: 10px;&quot; data-widthpercent=&quot;60.47&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;821&quot; data-origin-width=&quot;1209&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WTPjG/btsJQoCLK1m/u3lXCZOxtWkMTwpvKPZIm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWTPjG%2FbtsJQoCLK1m%2Fu3lXCZOxtWkMTwpvKPZIm1%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;1209&quot; height=&quot;821&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0OVeD/btsJQbw6wmj/6WFnfvvyjfsnCK9WC4gPM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0OVeD/btsJQbw6wmj/6WFnfvvyjfsnCK9WC4gPM0/img.png&quot; style=&quot;width: 39.066%;&quot; data-widthpercent=&quot;39.53&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;986&quot; data-origin-width=&quot;949&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0OVeD/btsJQbw6wmj/6WFnfvvyjfsnCK9WC4gPM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0OVeD%2FbtsJQbw6wmj%2F6WFnfvvyjfsnCK9WC4gPM0%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;949&quot; height=&quot;986&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;(좌) 오늘의집 UI 레퍼런스, (우) 실제 개발한 서비스 UI&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Nanum Gothic'; background-color: #ffd6d6;&quot;&gt;&lt;b&gt;배포&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;앞에서도 말했지만 처음엔 혼자 사용할 용도로 만들었기에 배포는 생각도 하지 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그래서 처음 형태는 local에서 모든 서버가(mongo 든, golang 이든) 통신하는 형태였다. 하지만 배포하기로 마음먹은 순간부터 드는 생각은 비용이었다. 어림잡아도 최소 1대의 서버는 돌려야 했다. 프론트엔드야 vercel로 무료 배포 할 수 있단 점이 다행이었지만, 백엔드는? ec2 내부에 docker-compose로 go 서버와 mongo 서버를 모두 돌리면, 당연히 위험하겠지만 비용은 아낄 수 있지 않을까. 내키진 않았지만 비용을 아끼려면 그 방법이 적합하다고 생각한 찰나에 작년 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;GopherCon&lt;/b&gt;&lt;/span&gt;에서 수빈님이 &lt;a href=&quot;https://www.youtube.com/watch?v=l42VTNbWKaI&amp;amp;t=712s&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;aws lambda로 go 애플리케이션을 배포한 내용&lt;/a&gt;이 불현듯 떠올랐다. 기본적으로 Lambda는 호출한 횟수만큼 과금되기 때문에 &lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;ec2&lt;/span&gt;처럼 띄워놓기만 해서 돈만 먹는 하마가 아니었다. 사이드 프로젝트 특성상 호출 횟수가 그리 크지 않을 것이라 예상했기 때문에 go 서버는 aws lambda로 배포하기로 결정했다. 그렇다면 mongoDB 는 어떡할 것인가? 이 역시 은혜로운 Atlas 사에서 프리티어로 제공하는 mongo cluster를 활용하기로 했다. 결국 총 3개의 서버를 모두 서버리스한 형태로 배포할 수 있게 됐다. 많은 사람들이 내 서비스를 사용해서 lambda 호출 횟수를 터뜨려주길 바라는 마음과 (거의) 공짜로 배포할 수 있어서 다행이라는 양립적인 마음이 동시에 들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1323&quot; data-origin-height=&quot;655&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/41LTv/btsJRE5BrML/Kj3rLzisLVpJmFyb2kDJb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/41LTv/btsJRE5BrML/Kj3rLzisLVpJmFyb2kDJb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/41LTv/btsJRE5BrML/Kj3rLzisLVpJmFyb2kDJb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F41LTv%2FbtsJRE5BrML%2FKj3rLzisLVpJmFyb2kDJb1%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;1323&quot; height=&quot;655&quot; data-origin-width=&quot;1323&quot; data-origin-height=&quot;655&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Nanum Gothic'; background-color: #ffd6d6;&quot;&gt;&lt;b&gt;피드백&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;감사하게도 많은 분들이 피드백을 주셨다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;사실 RSS는 이번 피드백을 통해 처음 알게 됐다. 현재 개발 방식은 모든 HTML의 구조를 파악하여 파싱하는 방식인데 RSS가 가능한 블로그는 별도로 처리하도로 개선해봐야겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daOQyB/btsJQmrqnb7/20kbrbDP6clxNLdQSwyDa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daOQyB/btsJQmrqnb7/20kbrbDP6clxNLdQSwyDa1/img.png&quot; data-alt=&quot;땡큐 천재 플러터 개발자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daOQyB/btsJQmrqnb7/20kbrbDP6clxNLdQSwyDa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaOQyB%2FbtsJQmrqnb7%2F20kbrbDP6clxNLdQSwyDa1%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;547&quot; height=&quot;170&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;211&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;땡큐 천재 플러터 개발자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ecMgP5/btsJRO79AHj/RABBRhZa0r1eRMcl1RLNgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ecMgP5/btsJRO79AHj/RABBRhZa0r1eRMcl1RLNgk/img.png&quot; data-alt=&quot;아리가또 마늘짱&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ecMgP5/btsJRO79AHj/RABBRhZa0r1eRMcl1RLNgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FecMgP5%2FbtsJRO79AHj%2FRABBRhZa0r1eRMcl1RLNgk%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;538&quot; height=&quot;143&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;아리가또 마늘짱&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;425&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buQsgg/btsJRQZbERu/GAhWuEpFZy8VZX2bHvYzl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buQsgg/btsJRQZbERu/GAhWuEpFZy8VZX2bHvYzl1/img.png&quot; data-alt=&quot;땡큐 사이퍼!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buQsgg/btsJRQZbERu/GAhWuEpFZy8VZX2bHvYzl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuQsgg%2FbtsJRQZbERu%2FGAhWuEpFZy8VZX2bHvYzl1%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;537&quot; height=&quot;210&quot; data-origin-width=&quot;425&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;땡큐 사이퍼!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;실제로 프론트&amp;nbsp;쪽 부분은&amp;nbsp;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://hooninedev.com/&quot;&gt;지훈&lt;/a&gt;이가&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://github.com/hugehoo/blog-scrapper-ui/pull/1&quot;&gt;PR을&lt;/a&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;남겨줘서 UX 측면에서 훨씬 개선됐다. 이 자리를&amp;nbsp;빌려&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-style=&quot;style3&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Nanum Gothic'; background-color: #ffd6d6;&quot;&gt;&lt;b&gt;마무리와 앞으로는&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 프로젝트로 수익화는 전혀 생각없고 (애초에 다른 회사 블로그 긁어모아놨으면서 돈 벌겠다는 건 좀..) 그저 사람들이 꾸준히 찾는 서비스가 되도록 계속 개선하고 싶다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 이미&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://techblogposts.com/ko&quot;&gt;유사한 서비스&lt;/a&gt;가 존재한다. 이미 잘 만들어진 서비스가 있지만, 그래도 내가 직접 만들어보는건 다르니까 개발하기로 했다. 확실히 직접 개발하면서 겪은 다양한 에러와 트러블 슈팅을 거치면서 역시 해보길 잘했다고 생각했다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;배포없이 나만 사용하는 서비스로 남겨두려했지만 지금 생각해보면 배포하길 백번 잘한 것 같다. 500 뜰까봐 조마조마하면서 모니터링도 하게 되고, 어떻게 개선할지, 어떤 기능을 추가할지 지속적으로 고민하게 된다. 고민이 깊어지면서 어떻게 문제를 해결할지 생각하는 자세도 깊어지는 걸 어렴풋이 느꼈다.&lt;br /&gt;아직은 너무 초기 단계의 서비스라 감상적인 회고를 남기기엔 이른 것 같다. 블로그 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;본문 검색 기능&lt;/b&gt;&lt;/span&gt;이 꽤 도전적일 것 같은데, 그것부터 해결하고 감상에 빠지기로 하자.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Backend</category>
      <author>Hugehoo</author>
      <guid isPermaLink="true">https://junior-datalist.tistory.com/396</guid>
      <comments>https://junior-datalist.tistory.com/396#entry396comment</comments>
      <pubDate>Mon, 30 Sep 2024 03:28:05 +0900</pubDate>
    </item>
    <item>
      <title>[Go] goroutine 과 channel 로 API 실행 시간 개선하기</title>
      <link>https://junior-datalist.tistory.com/395</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;목차&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 서론&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 동기식 호출&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 비동기 호출 : Goroutine 활용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 비동기 호출 개선 : Channel 활용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;- 결론&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;서론&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;여러 테크 기업의 기술 블로그를 읽기 좋아합니다. 다만 매번 여러 블로그 홈페이지를 방문하다 보니 자주 가는 블로그는 따로 모아서 읽고 싶은 생각이 들더라고요. 이를 위해 Go를 활용한 테크 블로그 스크래퍼를 개발하기로 했습니다. 이미 그런 서비스는 많지 않냐고요? 맞습니다. 그래도 한번 직접 해보고 싶었습니다 .&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;기업의 블로그들은 다양한 플랫폼을 활용합니다. Medium 부터 자체 블로그를 운영하는 곳까지, 다양한 방식의 기술 블로그를 제공합니다. 각기 다른 형태의 플랫폼을 사용하기 때문에 그에 맞는 스크래핑 방식을 사용해야 합니다. 저는 블로그 플랫폼에 맞는 스크래핑 구현체를 만들어 이를 차례로 호출하는 식으로 진행했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;현재는 다섯 개의 테크 블로그에 대한 스크래핑 구현체를 만들어 사용하고 있지만, 추후에는 외국의 테크 블로그도 가져올 수 있도록 확장할 계획입니다. 외부 API 호출이 5개보다 더 많아질 수 있는 상황이죠. 지금은 다섯 곳의 API만 호출하기에 API를 &lt;span style=&quot;text-align: start;&quot;&gt;동기식으로 &lt;/span&gt;호출해도 되지만, 앞으로 그 수가 늘어날 것을 대비해 API 를 병렬적으로 호출하기로 했습니다. 단순한 성능 개선을 넘어 Go 언어의 강력한 동시성 모델을 실제 프로젝트에 적용해보는 기회가 될 것이라 생각했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이 글은 다음과 같은 흐름으로 진행됩니다:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;우선 각각의 API를 블로킹 방식으로 가져오는 기존 방식에서, Go 언어의 특징인 &lt;code&gt;goroutine&lt;/code&gt; 을 활용한 비동기식 API 호출, 그리고 &lt;code&gt;channel&lt;/code&gt;을 활용한 최종 개선까지 단계별로 살펴보겠습니다. 각 단계에서 직면한 문제점과 그 해결 과정, 그리고 성능 개선의 결과를 구체적인 코드와 함께 보여드리겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;동기식 호출&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;각 블로그의 내부 호출 구현을 여기서 보여드리기엔 글의 주제와 거리가 있다고 생각되어 &lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/hugehoo/blog-gopher&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃헙 레포&lt;/a&gt;로 대체하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;우선 동기식 호출 부터 살펴보겠습니다. 코드는 간단합니다. 각 테크 블로그에서 스크래핑한 내용을 result 배열에 담습니다. result 배열은 스크래핑 한 모든 포스트를 담고 있는 배열로, 추후에 프론트단 개발 시 리스폰스 할 계획입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;result 에 담을 테크 블로그는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;카카오페이, 올리브영, 당근, 토스, 뱅크샐러드&lt;/b&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;pre id=&quot;code_1726159553855&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Post struct {
	Title   string
	Url     string
	Summary string
	Date    string
	Corp    compnay.Company
}

func main() {
	start := time.Now() // 시작 시간

	var result []Post
	result = callSynchronous(result)

	log.Println(&quot;Total :&quot;, len(result))
	elapsed := time.Since(start) // 경과 시간
	log.Printf(&quot;Execution Time: %s\n&quot;, elapsed)
}


// 동기식 처리
func callSynchronous(result []Post) []Post {
	result = append(result, kakaopay.CallApi()...)
	result = append(result, oliveyoung.CallApi()...)
	result = append(result, daangn.CallApi()...)
	result = append(result, toss.CallApi()...)
	result = append(result, banksalad.CallApi()...)
	return result
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;동기식 호출 결과 확인&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;동기식 호출은 &lt;b&gt;3.3초&lt;/b&gt;를 걸쳐 완료된 것을 확인했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;상당히 느린것을 볼 수 있는데 제 생각엔 특정 블로그를 스크래핑 할 때 블로킹이 오래 잡히는 곳이 있는 것 같습니다. 개별 블로그 스크래핑에 대한 성능 개선은 추후에 진행하도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;우선은 5개의 외부 API 를 동기식으로 호출할 때 3.3초가 넘는 시간이 걸린 것을 확인했고, 이를 고루틴을 활용해 &lt;code&gt;병렬처리방식&lt;/code&gt;으로 개선하기로 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;641&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qSOVw/btsJDtpCYd6/O6oOXUF8wCfZsqRCpnXAqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qSOVw/btsJDtpCYd6/O6oOXUF8wCfZsqRCpnXAqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qSOVw/btsJDtpCYd6/O6oOXUF8wCfZsqRCpnXAqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqSOVw%2FbtsJDtpCYd6%2FO6oOXUF8wCfZsqRCpnXAqk%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;665&quot; height=&quot;641&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;641&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;그림으로 표현하면 아래와 같습니다. 각각의 테크블로그를 동기식으로 호출하므로 하나의 블로그에서 스크래핑이 완료되어야 다음 블로그 API 를 호출하는 것을 나타냈습니다. (블로그의 호출 순서는 임의로 선정했습니다)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JrU0Q/btsJBrHvoP8/6hYXAvnHIQEg5lx4PCzuB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JrU0Q/btsJBrHvoP8/6hYXAvnHIQEg5lx4PCzuB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JrU0Q/btsJBrHvoP8/6hYXAvnHIQEg5lx4PCzuB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJrU0Q%2FbtsJBrHvoP8%2F6hYXAvnHIQEg5lx4PCzuB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;721&quot; height=&quot;399&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;526&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;비동기 호출 : Goroutine 활용&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;고루틴(Goroutine)&lt;/b&gt;은 Go 언어의 동시성 프로그래밍을 위한 경량 쓰레드입니다. 일반 쓰레드보다 훨씬 적은 메모리를 사용하며 동시에 수천 개의 고루틴을 실행할 수 있어 효율적인 동시성 처리가 가능합니다. &lt;code&gt;go&lt;/code&gt; 키워드를 사용하여 고루틴을 생성할 수 있고, 이를 활용해 기존의 동기식 블로킹 Api 호출을&amp;nbsp; 병렬적으로 수행하도록 개선해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;블로그를 차례로 호출하던 기존 코드와 다르게 고루틴을 활용한 코드는 이전보다 복잡해졌습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1726160467840&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func callGoroutine(result []Post) []Post {
	// (1)
	var mu sync.Mutex
	var wg sync.WaitGroup

	// (2)
	scrapers := []func() []Post{
		kakaopay.CallApi,
		oliveyoung.CallApi,
		daangn.CallApi,
		toss.CallApi,
		banksalad.CallApi,
	}

	for _, scraper := range scrapers {
		wg.Add(1)
		// (3)
		go func(scrapeFunc func() []Post) {
			defer wg.Done()

			// (4)
			posts := scrapeFunc()
            
			mu.Lock()
			result = append(result, posts...)
			mu.Unlock()
		}(scraper)
	}
	// (5)
	wg.Wait()
	return result
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;위 함수는 다음과 같은 방식으로 동작합니다:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;(1) sync.Mutex와 sync.WaitGroup을 사용하여 동시성 제어&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;(2) 블로그의 스크래핑 함수를 scrapers 슬라이스에 저장&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;(3) 각 스크래핑 함수에 대해 고루틴을 생성하여 병렬로 실행&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;(4) 고루틴 내에서: 스크래핑 함수를 실행하여 결과 리턴&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;(5) WaitGroup을 사용하여 모든 고루틴이 완료될 때까지 대기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;699&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIUND6/btsJB9M7Hm2/OsyBQN5g9djrWIVTFz9Kw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIUND6/btsJB9M7Hm2/OsyBQN5g9djrWIVTFz9Kw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIUND6/btsJB9M7Hm2/OsyBQN5g9djrWIVTFz9Kw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIUND6%2FbtsJB9M7Hm2%2FOsyBQN5g9djrWIVTFz9Kw0%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;648&quot; height=&quot;699&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;699&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;고루틴을 활용한 병렬 처리는 모든 API 호출이 동시에 실행되므로 전체 실행 시간이 크게 단축됩니다. 고루틴을 활용하여 &lt;b&gt;0.6초&lt;/b&gt; 만에 모든 블로그의 스크래핑을 완료하며 이전의 동기식 처리(3.3초)에 비해 실행시간을 크게 단축한 것을 확인할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;위 코드로도 충분히 나은 성능을 낼 수 있지만 살짝 아쉬운 부분도 존재합니다. 바로 &lt;code&gt;mutex&lt;/code&gt;를 사용해 공유 자원(result)에 락을 거는 부분입니다. 고루틴으로 API 호출은 병렬적으로 실행되지만 결과를 공유 자원에 담기 위해선 mutex 락을 사용해야 합니다. mutex 는 공유자원의 경쟁 조건을 방지하며 동시성 이슈를 해결하는 중요한 역할을 하지만, 결국 개별 고루틴이 블로킹 되는 효과를 초래합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1115&quot; data-origin-height=&quot;579&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lihzP/btsJCVN5VXZ/mLHRk03vjtiRrFSjREEJY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lihzP/btsJCVN5VXZ/mLHRk03vjtiRrFSjREEJY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lihzP/btsJCVN5VXZ/mLHRk03vjtiRrFSjREEJY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlihzP%2FbtsJCVN5VXZ%2FmLHRk03vjtiRrFSjREEJY0%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;691&quot; height=&quot;359&quot; data-origin-width=&quot;1115&quot; data-origin-height=&quot;579&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;고루틴으로 개선한 흐름은 위의 그림과 같습니다. 5개의 테크블로그 정보를 가져오기 위한 API 를 모두 병렬적으로 실행하는 것을 볼 수 있습니다.&amp;nbsp;&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;Go 의 Mutex 더 알아보기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;Go 의 뮤텍스(mutex)는 동시성 프로그래밍에서 중요한 동기화 도구로, 위 코드에서는 여러 고루틴이 공유 자원(result)에 동시 접근 하는 것을 방지합니다. mutex 는 Lock()을 호출하면 락을 획득하고, Unlock()을 호출하면 락을 해제합니다. 한 번에 하나의 고루틴만 뮤텍스의 락을 소유할 수 있으며, 다른 고루틴들은 락이 해제될 때까지 대기합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;mu.Lock()과 mu.Unlock()는&amp;nbsp;&lt;b&gt;개별 고루틴을 블로킹&lt;/b&gt;하는 동작입니다. 이 코드는&amp;nbsp;&lt;b&gt;메인 고루틴이 아닌&lt;/b&gt;&amp;nbsp;각 고루틴이 result에 데이터를 안전하게 추가하기 위해 **Mutex(뮤텍스)**를 사용해&amp;nbsp;&lt;b&gt;잠금을 걸고 해제하는&lt;/b&gt;&amp;nbsp;과정입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;mu.Lock()&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;각 고루틴은 result에 데이터를 추가하기 전에 mu.Lock()을 호출하여&amp;nbsp;&lt;b&gt;뮤텍스를 잠금&lt;/b&gt;니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;잠금이 걸린 고루틴이 result를 업데이트하는 동안 다른 고루틴은 mu.Lock()에서 대기(블로킹)합니다. 즉, mu.Lock()을 호출한 고루틴만이 result에 접근할 수 있고, 다른 고루틴은&amp;nbsp;&lt;b&gt;뮤텍스가 해제될 때까지 기다려야 합니다&lt;/b&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;mu.Unlock()&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;데이터 추가 작업이 완료되면 mu.Unlock()을 호출하여&amp;nbsp;&lt;b&gt;잠금을 해제&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;다른 고루틴 중 하나가 mu.Lock() 대기 상태에서 깨어나 result에 접근할 수 있게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;즉 개별 고루틴이 블로킹되면서 한&lt;/b&gt;&amp;nbsp;고루틴이 mu.Lock()을 걸고 result를 업데이트하는 동안,&amp;nbsp;&lt;b&gt;다른 고루틴들은 mu.Lock()에서 블로킹&lt;/b&gt;되어, result에 접근하지 못하고 대기합니다. 이 때&amp;nbsp;&lt;b&gt;메인 고루틴은 블로킹되지 않음&lt;/b&gt;: 메인 고루틴은 wg.Wait()에서 모든 고루틴이 완료되기를 기다리기 때문에, mu.Lock()과 관련된 동작에는 관여하지 않습니다. mu.Lock()과 mu.Unlock()은 고루틴 간의 경쟁 상태를 방지하기 위한 기법이며, 메인 고루틴이 직접적으로 블로킹되지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;비동기 호출 개선: channel 활용&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;고루틴을 활용하며 API 를 비동기 호출했지만 위의 방식은 mutex 락을 사용하므로 일부 구간에서 블로킹이 발생할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;뮤텍스(mutex)로 공유 자원의 접근을 제어하는 것은 일반적인 방법이지만, 여러 고루틴이 빈번하게 공유 자원에 접근하는 경우엔 뮤텍스로 인한 블로킹이 전체 프로그램의 성능을 저하시킬 수 있다고 생각했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이러한 상황에서 Go는 &lt;code&gt;채널(channel)&lt;/code&gt;이라는 대안을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;Go 의 채널은 고루틴을 연결해주는 &lt;code&gt;통로&lt;/code&gt;입니다. 채널은 양방향으로 데이터를 전달할 수 있으며, 아래 이미지처럼 고루틴은 동일한 채널을 이용해 데이터를 교환할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1195&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEaSVE/btsJCBwt4wl/aCkKQHRGG47AIqVAuLVPH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEaSVE/btsJCBwt4wl/aCkKQHRGG47AIqVAuLVPH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEaSVE/btsJCBwt4wl/aCkKQHRGG47AIqVAuLVPH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEaSVE%2FbtsJCBwt4wl%2FaCkKQHRGG47AIqVAuLVPH0%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;1195&quot; height=&quot;291&quot; data-origin-width=&quot;1195&quot; data-origin-height=&quot;291&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;채널을 사용하면 각 데이터를 개별 고루틴에서 처리하고 그 결과를 채널을 통해 전송합니다. 메인 고루틴은 채널에서 결과를 수집합니다. 결국 공유자원에 대한 모든 접근을 단일 고루틴으로 관리할 수 있기에, 채널을 사용하면 복잡한 동기화 로직 없이도 고루틴 간의 작업을 조율할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;*   그렇다고 채널이 뮤텍스를 완전히 대체할 수 있단 의미는 아닙니다. 채널은 주로 고루틴 간의 통신과 작업 조율에 사용되며, 뮤텍스는 짧은 시간 동안의 공유 자원 보호에 효과적입니다. 둘은 각각 적절히 사용할 때가 있으며 때로는 둘을 함께 사용하는것이 해결책이 될 수도 있다고 생각합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;다음은 채널을 이용해 개선된 코드입니다:&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1726161854835&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func callGoroutineChannel(result []Post) []Post {
	scrapers := []func() []Post{
		kakaopay.CallApi,
		oliveyoung.CallApi,
		daangn.CallApi,
		toss.CallApi,
		banksalad.CallApi,
	}
	resultChan := make(chan []Post, len(scrapers))

	var wg sync.WaitGroup
	for _, scraper := range scrapers {
		wg.Add(1)
		go func(scrapeFunc func() []Post) {
			defer wg.Done()
			resultChan &amp;lt;- scrapeFunc()
		}(scraper)
	}

	go func() {
		wg.Wait()
		close(resultChan)
	}()

	for posts := range resultChan {
		result = append(result, posts...)
	}

	return result
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;1. 버퍼된 채널 생성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;code&gt;resultChan := make(chan []Post, len(scrapers))&lt;/code&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;버퍼의 크기는 scarppers 배열의 길이로 설정된 &lt;b&gt;resultChan &lt;/b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;채널&lt;/span&gt;을 생성합니다. &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이 채널은 각 고루틴이 스크래핑한 결과를 전달하는 통로로 사용됩니다. 버퍼된 채널을 이용함으로, 고루틴이 &lt;b&gt;결과를 채널에 전송하는 과정에서 블로킹&lt;/b&gt;되지 않도록 합니다. 버퍼의 크기가 충분하다면, 고루틴이 &lt;b&gt;결과를 즉시 전송&lt;/b&gt;하고 종료될 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;2. 고루틴으로 비동기 처리&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;각 스크래핑 함수를 고루틴으로 병렬 실행하며, 각 고루틴은 스크래핑 결과를 resultChan에 전달합니다. 모든 고루틴이 완료되면 WaitGroup을 사용해 개별 고루틴의 작업이 끝났는지 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;3. 채널 수신 및 결과 수집&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;code&gt;for posts := range resultChan&lt;/code&gt; 구문을 통해 채널에서 결과를 &lt;b&gt;순차적으로 수신&lt;/b&gt;합니다. 채널이 닫힐 때까지 이 동작이 반복되며, 채널이 닫히면 for 루프도 종료됩니다. 이는 range 구문이 &lt;code&gt;&amp;lt;- resultChan&lt;/code&gt; 과 동일한 수신 동작을 반복적으로 처리하는 것으로 이해할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;4. WatiGroup 과 채널 종료&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;별도의 고루틴에서 &lt;code&gt;wg.Wait()&lt;/code&gt;를 호출해, 모든 고루틴이 완료될 때 채널을 &lt;code&gt;close(resultChan)&lt;/code&gt;로 닫습니다. 채널이 닫히면 더 이상 새로운 값이 전송되지 않으며, 채널의 데이터는 안전하게 모두 처리됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HL8Nl/btsJCD7WBZR/TbkDCZhJZHFHkLuKLKSQ20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HL8Nl/btsJCD7WBZR/TbkDCZhJZHFHkLuKLKSQ20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HL8Nl/btsJCD7WBZR/TbkDCZhJZHFHkLuKLKSQ20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHL8Nl%2FbtsJCD7WBZR%2FTbkDCZhJZHFHkLuKLKSQ20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;730&quot; height=&quot;740&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;channel 을 사용한 결과는 &lt;b&gt;0.5초&lt;/b&gt;로 mutex 를 사용한 방식과 비교했을 때 생각보다 유의미한 개선은 없습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;현재 프로젝트 규모에서는 mutex 락이 성능에 그리  큰 영향을 미친다고 보기 어려울 수 도 있고, 네트워크 지연 같은 외부 요인도 의심해 볼 수 있습니다. 그 외에도 각 고루틴 생성과 채널 통신시 발생하는 약간의 오버헤드도 고려해볼수 있습니다. 이는 오버헤드와 뮤텍스 사용 시 발생하는 성능 저하가 크게 다르지 않을 수 있기 때문에, 작업의 규모가 작거나 고루틴에서 수행하는 작업이 길지 않은 현재의 프로젝트에서는 이런 오버헤드가 더 두드러질 수 있습니다. 해당 부분의 개선은 다음 포스트에서 더 다뤄보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhiZcg/btsJDzXQYqo/bDmFIHW3k7wjDvNRu4nkrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhiZcg/btsJDzXQYqo/bDmFIHW3k7wjDvNRu4nkrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhiZcg/btsJDzXQYqo/bDmFIHW3k7wjDvNRu4nkrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhiZcg%2FbtsJDzXQYqo%2FbDmFIHW3k7wjDvNRu4nkrK%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;728&quot; height=&quot;705&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;동기식 보다 비동기식이 빠른건 사실 너무 당연한 얘기지만 ㅎㅎ,, 이번 프로젝트를 통해 Go 언어의 동시성 모델을 실제로 적용해볼 수 있었습니다. 스크래퍼 호출 과정에서 동기식 호출부터 goroutine을 활용한 비동기 처리, 그리고 channel을 이용한 개선까지 단계적으로 접근하며 각 방식의 특징을 이해할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;Goroutine 을 사용한 간단하고 효율적인 동시성을 구현할 수 있었고, mutex 를 사용한 공유 자원 접근 제어의 중요성과 한계를 파악했습니다. 나아가 channel 을 활용해 lock 을 제거하여 더욱 유연하고 Go 스러운(?) 동시성 처리방식에 대해서도 알게 됐습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #000000;&quot;&gt;이번 글에서는 API 호출과정을 점진적으로 개선하는 과정만 다뤘는데, 추후에는 개별 블로그 스크래핑 성능 최적화와 고루틴과 채널 사용 시 오버헤드 최소 방안을 더 고려한 글을 작성해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/  Go</category>
      <category>channel</category>
      <category>GO</category>
      <category>goroutine</category>
      <author>Hugehoo</author>
      <guid isPermaLink="true">https://junior-datalist.tistory.com/395</guid>
      <comments>https://junior-datalist.tistory.com/395#entry395comment</comments>
      <pubDate>Fri, 13 Sep 2024 17:28:40 +0900</pubDate>
    </item>
  </channel>
</rss>