<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Distributed_system on Jiho Kim</title><link>https://blog.wlgh7407.com/tags/distributed_system/</link><description>Recent content in Distributed_system on Jiho Kim</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Wed, 02 Sep 2026 09:56:08 +0900</lastBuildDate><atom:link href="https://blog.wlgh7407.com/tags/distributed_system/index.xml" rel="self" type="application/rss+xml"/><item><title>Lab 1 MapReduce</title><link>https://blog.wlgh7407.com/posts/project/mit_6.5840_distributed_systems/260902_project_lab-1-mapreduce/</link><pubDate>Wed, 02 Sep 2026 09:56:08 +0900</pubDate><guid>https://blog.wlgh7407.com/posts/project/mit_6.5840_distributed_systems/260902_project_lab-1-mapreduce/</guid><description>&lt;p&gt;Go 언어로 &lt;a class="link" href="https://blog.wlgh7407.com/posts/dev/260828_dev_mapreduce-simplified-data-processing-on-large-clusters-2004/" &gt;MapReduce&lt;/a&gt;의 논문 내용을 구현해보자. 아래 내용은 실시간성 시행착오&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;&amp;hellip;일단 제일 큰 이슈. 뭐부터 해야하지?
&lt;ul&gt;
&lt;li&gt;먼저 &lt;code&gt;rpc.go&lt;/code&gt;에서 요청/응답 형태를 만들고, 응답이 왔을때 Map작업을 하는거까지부터 만들어보자.
&lt;ul&gt;
&lt;li&gt;여기서 &lt;code&gt;ExampleArgs&lt;/code&gt;가 있어서 찾아보니까, 요청/응답을 &lt;code&gt;Args&lt;/code&gt;, &lt;code&gt;Reply&lt;/code&gt;로 하는게 관례인 것 같다.&lt;/li&gt;
&lt;li&gt;따라서 우리도 &lt;code&gt;TaskArgs&lt;/code&gt;, &lt;code&gt;TaskReply&lt;/code&gt; 로 해볼까?&lt;/li&gt;
&lt;li&gt;일단 &lt;code&gt;TaskReply&lt;/code&gt;에서, 무슨 정보가 필요할까?
&lt;ul&gt;
&lt;li&gt;어떤 작업인지 알려줘야하고 (Map or Reduce)
&lt;ul&gt;
&lt;li&gt;여기서 파이선 enum같은게 가능할까? 두 string중 하나로 정리하고싶은데. 아니면 bool로 할까?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;어떤 입력파일을 처리해야할지 알려줘야 하고
&lt;ul&gt;
&lt;li&gt;이건 Map, Reduce 둘다 작업 번호로 처리할 수 있을 것 같다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이 두가지면 될거같은데?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이후 &lt;code&gt;worker.go&lt;/code&gt;에서 이를 받아줘야할 것 같다.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CallExample&lt;/code&gt;이 예시로 있으니, 우리도 &lt;code&gt;CallTask&lt;/code&gt;로 이름짓고 하면 될듯&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;아 이게 다 붙어있네. &lt;code&gt;CallTask&lt;/code&gt;는 &lt;code&gt;coordinator.go&lt;/code&gt;에서 잘 반환해줘야하는데..
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Coordinator&lt;/code&gt; struct에서 Task의 개수와 어디까지 보냈는지같은걸 잘 관리해야겠다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;실제로 실행하면서 한번 해보자.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;go run mrcoordinator.go sock123 pg-*.txt&lt;/code&gt; 로 코디네이터를 만들면
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;coordinator.go&lt;/code&gt; 의 &lt;code&gt;MakeCoordinator&lt;/code&gt; 이 호출되고,&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;go run mrworker.go wc.so sock123&lt;/code&gt; 로 워커를 만들면
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;worker.go&lt;/code&gt;의 &lt;code&gt;Worker&lt;/code&gt; 함수가 호출된다.&lt;/li&gt;
&lt;li&gt;구조를 보니 map, reduce 함수는 만들어져있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;두 함수를 잘 채워주자!&lt;/li&gt;
&lt;li&gt;&lt;img
 src="https://blog.wlgh7407.com/images/Pasted%20image%2020260902105223.png"
 alt="Image Description"loading="lazy"&gt;&lt;/li&gt;
&lt;li&gt;일단 동시성 없이, Map요청을 우선으로 응답하고 Reduce연산을 응답하는 구조를 만들었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이제 작업을 붙여야한다.
&lt;ul&gt;
&lt;li&gt;지금 하다보니, &lt;code&gt;ExampleReply&lt;/code&gt;에서 &lt;code&gt;filename&lt;/code&gt;도 필요할 것 같다.&lt;/li&gt;
&lt;li&gt;이후 &lt;code&gt;mrsequential.go&lt;/code&gt;에서 하는것과 같이 file을 읽어들여서 &lt;code&gt;mapf&lt;/code&gt; 함수를 먹이면, 다음과 같이 잘 수행됨을 확인할 수 있다!&lt;/li&gt;
&lt;li&gt;&lt;img
 src="https://blog.wlgh7407.com/images/Pasted%20image%2020260902132530.png"
 alt="Image Description"loading="lazy"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이제 이 결과를 nReduce개의 중간 파일에 저장하자.
&lt;ul&gt;
&lt;li&gt;아잇;; NReduce도 RPC에서 전달받아야했다.&lt;/li&gt;
&lt;li&gt;&lt;img
 src="https://blog.wlgh7407.com/images/Pasted%20image%2020260902135414.png"
 alt="Image Description"loading="lazy"&gt;&lt;/li&gt;
&lt;li&gt;잘 데려와서 진행하면, 중간 버킷을 이용해서 위와 같이 &lt;code&gt;mr-M-R&lt;/code&gt; 파일명으로 모두 저장해줄 수 있다!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Reduce 함수도 같은 방식으로 만들어주면 된다.
&lt;ul&gt;
&lt;li&gt;&lt;img
 src="https://blog.wlgh7407.com/images/Pasted%20image%2020260902141712.png"
 alt="Image Description"loading="lazy"&gt;&lt;/li&gt;
&lt;li&gt;깔끔하게 10개의 out파일이 잘 나온다!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이제 자랑스럽게 &lt;code&gt;make mr&lt;/code&gt;을 돌려보면, 다음과같은 오류들을 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;WARNING: DATA RACE
Read at 0x00c00013f1f0 by main goroutine:
 6.5840/mr.(*Coordinator).Done()
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;이는 채점 환경이 Worker을 3개 돌리는 고루틴 환경이기 때문!
&lt;ul&gt;
&lt;li&gt;우리는 이제 동시성도 제어해야한다.&lt;/li&gt;
&lt;li&gt;mutex같은걸로 보호하면서 수행해보자.&lt;/li&gt;
&lt;li&gt;뭘 보호해야하지?&lt;/li&gt;
&lt;li&gt;DATA RACE가 나는 상황을 보면, TaskRPC에서 Coordinator을 참조할때 문제가 생기는 것 같다.
&lt;ul&gt;
&lt;li&gt;따라서 TaskRPC 하나가 돌동안 잠궈주면 되지 않을까?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;여기까지 잘 하면 &lt;code&gt;--- PASS: TestWc (7.95s)&lt;/code&gt; 를 일단 받을 수 있었다!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이제 문제는 실패한 태스트 혹은 할당의 문제..
&lt;ul&gt;
&lt;li&gt;현재 구조에서는 한번 보낸 태스크들은 모두 버그없이 완료되었다고 생각한다.
&lt;ul&gt;
&lt;li&gt;Midx정도로만 관리했기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이제 현재 업무가 어떤 상태인지도 정의해야할 것 같은데..
&lt;ul&gt;
&lt;li&gt;두가지 아이디어가 생각난다.
&lt;ul&gt;
&lt;li&gt;첫번째는 배열/슬라이스로, $[M_1, M_2, M_3, ..., M_{m-1}, M_M]$ 에 대해 각 $(0, 1, 2)$ 같은 값을 부여해서, 할당 없음 / 작업중 / 작업 완료 세가지로 나누는 것이다!&lt;/li&gt;
&lt;li&gt;두번째는 set.. 이지만 Go에는 없으므로 map같은걸로 위의 세가지 그룹에 대한 집합을 관리하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;뭐가됐든 RPC는 하나가 더 필요할 것 같다. 성공을 관리하는거!&lt;/li&gt;
&lt;li&gt;그리고 set은 어떻게 구현할지 바로 생각이 안나니까 일단 슬라이스로 시작해보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;슬라이스로 관리하고, 함수 하나를 정의해야해서 일이 있으면 해당 인덱스, 할일은 없지만 작업중인 일이 남아있으면 -1, 모든 일이 끝났으면 -2를 반환하도록 하였다.
&lt;ul&gt;
&lt;li&gt;추가적으로 실수가 있었던 kv레코드 / json인코딩 저장 방식까지 수정하니까 (공식 가이드에있었음) 다음과 같이 꽤 좋은 결과를 얻을 수 있었다!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;=== RUN TestWc
--- PASS: TestWc (10.08s)
=== RUN TestIndexer
--- PASS: TestIndexer (5.87s)
=== RUN TestMapParallel
--- PASS: TestMapParallel (8.04s)
=== RUN TestReduceParallel
--- PASS: TestReduceParallel (10.05s)
=== RUN TestJobCount
--- PASS: TestJobCount (12.06s)
=== RUN TestEarlyExit
cmp: EOF on mr-wc-initial.txt
 util.go:163: reduce output changed: mr-wc-initial.txt vs mr-wc-final.txt
--- FAIL: TestEarlyExit (7.05s)
=== RUN TestCrashWorker
^Csignal: interrupt
FAIL 6.5840/mr 263.011s
make: *** [mr] Error 1
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;아마도 EarlyExit, CrashWorker 둘다 이름에서 알 수 있다시피 내결함성에 관한걸 테스트해야할 것 같다.
&lt;ul&gt;
&lt;li&gt;만약에 worker이 일을 물고 죽었다면? 어떡하지? 같은거지.&lt;/li&gt;
&lt;li&gt;이것도 사이트를 보면 10초정도 후에 백업 태스크가 살아야한다고 알려준다. 이를 구현해보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;저걸 해서 CrashWorker까진 고쳤지만 EarlyExit이 안돈다! 이게 무슨 일일까?
&lt;ul&gt;
&lt;li&gt;EarlyExit은 worker가 Task를 받자마자 죽어버리는거같은 일이다.
&lt;ul&gt;
&lt;li&gt;그런데 아예 죽지 않고 살아있다면?&lt;/li&gt;
&lt;li&gt;그래서 동시에 두 Worker이 같은 일을 하고있어서, &lt;code&gt;os.Create&lt;/code&gt;를 같이 써버린다면?
&lt;ul&gt;
&lt;li&gt;분명 내용이 유실되겠지!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그렇다면, 임시파일에 쭈루룩 쓰다가 모두 쓴 다음에 &lt;code&gt;os.Rename&lt;/code&gt;같은걸 이용해보자.
&lt;ul&gt;
&lt;li&gt;이거 문제가 아닌데..&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;아니;; 다른 원인을 찾다보니 Reduce 작업에서 조금 실수해서 완료처리를 잘못하고 있었다.
&lt;ul&gt;
&lt;li&gt;이걸 고치고 깔끔하게 완료!
&lt;img
 src="https://blog.wlgh7407.com/images/Pasted%20image%2020260903211357.png"
 alt="Image Description"loading="lazy"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;고친후에 깔끔히 만점!&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="알게된-사실들"&gt;&lt;a href="#%ec%95%8c%ea%b2%8c%eb%90%9c-%ec%82%ac%ec%8b%a4%eb%93%a4" class="header-anchor"&gt;&lt;/a&gt;알게된 사실들
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;struct 내부의 메서드를 대문자로 시작하지 않으면 다른데서 접근조차 안된다! 저장부터 안들어가는듯&lt;/li&gt;
&lt;li&gt;Mutex Lock를 걸면 자동으로 해당 데이터에 대한 접근을 막는게 아니라, 다음 Mutex의 Lock까지를 대기한다.
&lt;ul&gt;
&lt;li&gt;따라서 쓰기에서만 Lock을 걸면 안되고, 읽을때도 하나하나 다 걸어줘야한다!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;함수 &lt;code&gt;func1&lt;/code&gt;이 함수 &lt;code&gt;func2&lt;/code&gt;를 호출하는 경우, 그리고 둘다 &lt;code&gt;c&lt;/code&gt;를 사용한다고 생각해보자.
&lt;ul&gt;
&lt;li&gt;이때 둘다 Mutex Lock을 걸어버리면, Deadlock이 발생한다.&lt;/li&gt;
&lt;li&gt;따라서, 이럴때는 내부 헬퍼함수같은 느낌으로 진행할 수 있겠다.
&lt;ul&gt;
&lt;li&gt;이때 헬퍼함수는 뒤에 Locked같은 접미사를 붙이는게 관례라고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;공식 가이드에도 있는 내용인데, json으로 저장하기 위해선 k-v 텍스트 레코드대신 json 인코딩된 레코드로 저장하는게 좋다고 한다. 왜지?
&lt;ul&gt;
&lt;li&gt;텍스트 포맷은 구분자에 의존한다 (개행)&lt;/li&gt;
&lt;li&gt;그런데 Key나 Value에 이미 그런 구분자가 들어있다면?
&lt;ul&gt;
&lt;li&gt;따라서 json형태로 저장하면 훨씬 안전하다!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>MapReduce: Simplified Data Processing on Large Clusters (2004)</title><link>https://blog.wlgh7407.com/posts/dev/260828_dev_mapreduce-simplified-data-processing-on-large-clusters-2004/</link><pubDate>Fri, 28 Aug 2026 15:18:47 +0900</pubDate><guid>https://blog.wlgh7407.com/posts/dev/260828_dev_mapreduce-simplified-data-processing-on-large-clusters-2004/</guid><description>&lt;h2 id="-상세-정리"&gt;&lt;a href="#-%ec%83%81%ec%84%b8-%ec%a0%95%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;📝 상세 정리
&lt;/h2&gt;&lt;h3 id="mapreduce"&gt;&lt;a href="#mapreduce" class="header-anchor"&gt;&lt;/a&gt;MapReduce
&lt;/h3&gt;&lt;p&gt;MapReduce는 대규모 데이터셋을 처리하고 생성하기 위한 프로그래밍 모델이자 구현체&lt;/p&gt;
&lt;p&gt;2000년대 초반 구글은 크롤링된 데이터, 웹 요청 로그 등 방대한 양의 raw 데이터들을 처리하여 Inverted Index, 그래프 구조, 크롤링도니 페이지 수와 요약 등 많은 프로그램들을 구현했다.
하지만 이런 계산들이 대부분 개념적으로는 간단한 반면 입력 데이터의 양이 매우 방대해서 수백, 수천대의 머신에 계산을 분산시키고자 하였다. 계산을 병렬화하고 데이터를 분산시키고, 장애를 처리하는 등의 과정들의 문제가 붙으면서 단순한 계산은 복잡한 코드가 될 가능성이 높았고, 이에 따라 Google은 이런 세부사항들을 라이브러리 내에 숨기는 새로운 &lt;strong&gt;추상화&lt;/strong&gt;를 설계했다.&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://blog.wlgh7407.com/posts/dev/260823_dev_the-google-file-system-2003/" &gt;GFS&lt;/a&gt; 에서 생각했던 것들과 같이 결함 허용, 데이터 분산 및 부하 분산 등과 같은 개념들을 사용해서 분산 컴퓨팅을 구현해낸 것이고, 아래에서 자세히 알아보자.&lt;/p&gt;
&lt;h3 id="아키텍쳐"&gt;&lt;a href="#%ec%95%84%ed%82%a4%ed%85%8d%ec%b3%90" class="header-anchor"&gt;&lt;/a&gt;아키텍쳐
&lt;/h3&gt;&lt;h4 id="map-reduce"&gt;&lt;a href="#map-reduce" class="header-anchor"&gt;&lt;/a&gt;Map, Reduce
&lt;/h4&gt;&lt;p&gt;MapReduce는 크게 아래 두가지 함수로 이루어진다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;{k, v}쌍을 처리하여 중간 {k, v} 쌍을 만드는 &lt;code&gt;map&lt;/code&gt; 함수&lt;/li&gt;
&lt;li&gt;모든 중간값들을 병합하는 &lt;code&gt;reduce&lt;/code&gt;함수&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예시로 들자면 다음과 같다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;map&lt;/span&gt;(key: str, value: str): &lt;span style="color:#75715e"&gt;# key: document name, value: document content&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; w &lt;span style="color:#f92672"&gt;in&lt;/span&gt; value: &lt;span style="color:#75715e"&gt;# w: words&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		EmitIntermediate(w, &lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;reduce&lt;/span&gt;(key: str, values: Iter): &lt;span style="color:#75715e"&gt;# key: a word, value: a list of counts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	result: int &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; v &lt;span style="color:#f92672"&gt;in&lt;/span&gt; values:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		result &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; ParseInt(v)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	Emit(AsString(result))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;간단하게 본문들에서 단어의 발생 횟수를 출력하는 예시이다.&lt;/p&gt;
&lt;p&gt;Grep, URL 접속 빈도, 웹링크 그래프 등도 위와 같이 간단하게 추상화 할 수 있을 것이다.&lt;/p&gt;
&lt;h4 id="아키텍쳐-1"&gt;&lt;a href="#%ec%95%84%ed%82%a4%ed%85%8d%ec%b3%90-1" class="header-anchor"&gt;&lt;/a&gt;아키텍쳐
&lt;/h4&gt;&lt;p&gt;&lt;img
 src="https://blog.wlgh7407.com/images/Pasted%20image%2020260828154423.png"
 alt="Image Description"loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;아키텍쳐의 모습과 실제로 어떤 순서로 구현되는지 알아보자.&lt;/p&gt;
&lt;ol start="0"&gt;
&lt;li&gt;사용자는 Map작업의 개수 M, Reduce 작업의 개수 R, 그리고 Worker Machine의 개수를 선택한다.
&lt;ul&gt;
&lt;li&gt;이때 흔히 M = 200,000 R = 5,000, 그리고 워커 머신은 2,000대를 선택한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;User Program의 MapReduce 라이브러리는 먼저 Input files를 조각당 16~64MB 크기의 M개 조각으로 분할한다. 그 후 프로그램을 모든 워커에 복제하여 실행한다.&lt;/li&gt;
&lt;li&gt;프로그램 복제본중 하나는 Master가 된다. 나머지는 작업을 할당받을 worker이 된다. 마스터는 앞으로 M개의 map 작업과 R개의 reduce 작업을 worker에 할당할 것이다.&lt;/li&gt;
&lt;li&gt;map 작업을 할당받은 worker은 split의 내용을 읽고 계산하여 중간 값을 메모리에 저장한다.&lt;/li&gt;
&lt;li&gt;주기정으로 메모리에 저장된 값은 로컬 디스크에 기록되고, 이 결과는 파티셔닝 함수에 의해 R개의 영역으로 분할된다. 이 로컬에 저장된 값의 위치는 Master에게 전달되고, Master는 이후 reduce worker이 요청할때 이 위치를 전달한다.&lt;/li&gt;
&lt;li&gt;reduce worker이 Master에게 이 위치를 전달받으면 reduce worker은 map worker의 로컬 디스크로부터 이 데이터들을 읽어온다. 그리고 모든 데이터를 받아온 후 중간 key를 기준으로 데이터를 정렬한다.&lt;/li&gt;
&lt;li&gt;reduce worker은 reduce작업을 수행한다.&lt;/li&gt;
&lt;li&gt;모든 map 작업과 reduce 작업이 끝나면 master은 User Program에게 전달한다.
&lt;ul&gt;
&lt;li&gt;그 결과는 reduce 작업당 하나씩 R개의 출력파일로 저장된다.&lt;/li&gt;
&lt;li&gt;다른 MapReduce 호출의 입력으로 전달할수도 있고, 다른 분산 애플리케이션에서 사용할수도 있으니까&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;또한 더 좋은 성능을 위해 GFS에 저장된 파일들은 Locality를 살려 해당 머신에서 map 작업을 우선 수행할 수 있도록 하고, 파티셔닝 함수와 정렬 보장등의 테크닉을 추가로 적용한다.&lt;/p&gt;
&lt;h4 id="결함-허용"&gt;&lt;a href="#%ea%b2%b0%ed%95%a8-%ed%97%88%ec%9a%a9" class="header-anchor"&gt;&lt;/a&gt;결함 허용
&lt;/h4&gt;&lt;p&gt;분산되는 방식은 알겠는데, 사실 저게 깔끔하게 진행되기만 한다면 아무 문제가 없겠지만 수천대의 컴퓨터를 사용하는 이상 결함이 발생할 가능 성을 없앨 수는 없겠다. 하나하나 알아보자.&lt;/p&gt;
&lt;h5 id="worker-장애"&gt;&lt;a href="#worker-%ec%9e%a5%ec%95%a0" class="header-anchor"&gt;&lt;/a&gt;worker 장애
&lt;/h5&gt;&lt;p&gt;Master은 주기적으로 모든 worker에게 ping을 보내고, 일정 시간동안 worker로부터 응답이 없으면 해당 워커는 실패한 것으로 표시한다.
이후 해당 워커가 수행한 모든 map 작업은 초기 idle 상태로 재설정된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;실패한 머신의 로컬 디스크에 접근할 수 없기 때문!&lt;/li&gt;
&lt;li&gt;reduce 결과는 다른 곳에 접근 가능하니 재실행하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="master-장애"&gt;&lt;a href="#master-%ec%9e%a5%ec%95%a0" class="header-anchor"&gt;&lt;/a&gt;Master 장애
&lt;/h5&gt;&lt;p&gt;Master에 체크포인트를 둬서 새로운 복사본을 시작하는 방식으로 구현할 수 있겠지만, Worker과달리 Master은 한 대만 있기 때문에 장애가 발생할 가능성이 낮다. 따라서 MapReduce 계산을 중단한다.&lt;/p&gt;
&lt;h5 id="straggler"&gt;&lt;a href="#straggler" class="header-anchor"&gt;&lt;/a&gt;Straggler
&lt;/h5&gt;&lt;p&gt;머신 하나가 map 혹은 reduce 작업을 수행하는데 모종의 이유로 너무 오래걸린다면, MapReduce 작업이 완료단계에 가까워질때즈음 마스터는 진행중인 나머지 작업들에 대해 백업 실행을 예약한다.
이후 기존 실행이나 백업 실행중 어느 하나라도 완료되면 해당 작업은 완료된 것으로 표시한다.&lt;/p&gt;
&lt;h3 id="결론"&gt;&lt;a href="#%ea%b2%b0%eb%a1%a0" class="header-anchor"&gt;&lt;/a&gt;결론
&lt;/h3&gt;&lt;p&gt;MapReduce는 위와 같이 간단한 모델과 아이디어로 무거운 작업을 성공적으로 분산시키는데 성공했다. 구글은 이를 이용해 인덱싱 코드가 cpp 3800줄에서 700줄로 줄일 수 있었을 정도였으며, 프로그램 작성은 물론 성능까지 챙기는데에 성공했다.&lt;/p&gt;
&lt;h2 id="질문-사항"&gt;&lt;a href="#%ec%a7%88%eb%ac%b8-%ec%82%ac%ed%95%ad" class="header-anchor"&gt;&lt;/a&gt;❔질문 사항
&lt;/h2&gt;&lt;details class="alert alert-question" &gt;
 &lt;summary class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;❓&lt;/span&gt;
 &lt;span class="alert-title"&gt;map과 reduce작업의 할당 부분에서 reduce 작업은 map 작업이 모두 끝나야만 가능할텐데 (R개의 파티션으로 나눠지니까), reduce 작업이 어떤 머신에 먼저 할당되어있다면 해당 worker의 CPU는 놀고 있는게 될거고 (Map 작업이 끝나지 않았으니까) 그렇다고 모든 map worker이 끝난 후에 reduce worker이 할당된다면 네트워크 대역폭을 손해보고 있을텐데, 디테일한 구현이 어떻게 되어있을까?&lt;/span&gt;
 &lt;/summary&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;맞는 말이다. 이를 위해 다음과 같이 할당된다고 보면 된다.
초반 -&amp;gt; 2000대 대부분이 map 수행
중반 -&amp;gt; 대부분 머신이 map을 수행하되, 일부 머신이 먼저 reducer이 되어 shuffle 수행
map 완료 직전 -&amp;gt; map straggler 처리, 더 많은 머신이 reducer 수행
모든 map 완료 -&amp;gt; 조기 시작 reducer은 먼저 reduce 수행, 나머지 reducer은 shuffle 작업 시작
이렇게 하면 클러스터 전체 관점에서 어느정도 파이프라이닝이 되면서 훨씬 빠르다.&lt;/p&gt;
 &lt;/div&gt;
 &lt;/details&gt;
&lt;h2 id="-참고-자료"&gt;&lt;a href="#-%ec%b0%b8%ea%b3%a0-%ec%9e%90%eb%a3%8c" class="header-anchor"&gt;&lt;/a&gt;🔗 참고 자료
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Dean, J., &amp;amp; Ghemawat, S. (2004). MapReduce: Simplified data processing on large clusters. &lt;em&gt;In Proceedings of the 6th Conference on Symposium on OSDI (Operating Systems Design and Implementation)&lt;/em&gt; (Vol. 6, pp. 10-10).&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>The Google File System (2003)</title><link>https://blog.wlgh7407.com/posts/dev/260823_dev_the-google-file-system-2003/</link><pubDate>Sun, 23 Aug 2026 17:20:45 +0900</pubDate><guid>https://blog.wlgh7407.com/posts/dev/260823_dev_the-google-file-system-2003/</guid><description>&lt;h2 id="-상세-정리"&gt;&lt;a href="#-%ec%83%81%ec%84%b8-%ec%a0%95%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;📝 상세 정리
&lt;/h2&gt;&lt;h3 id="google-file-system"&gt;&lt;a href="#google-file-system" class="header-anchor"&gt;&lt;/a&gt;Google File system
&lt;/h3&gt;&lt;p&gt;구글은 급증하는 데이터 처리 요구사항을 충족하기 위해 &lt;strong&gt;GFS(Google File System)&lt;/strong&gt; 을 설계하고 구현했다. GFS가 최초의 분산 파일 시스템 개념은 아니지만, 몇가지 다른 접근 방식들을 통해 더 효율적인 분산 시스템을 구현하고자 하였다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;컴포넌트의 장애는 일반적인 현상이다.&lt;/li&gt;
&lt;li&gt;파일의 크기는 꽤 크다. (수 GB 단위의 파일이 흔하다.)&lt;/li&gt;
&lt;li&gt;대부분의 파일은 덮어쓰기보다 새로운 데이터를 추가하는 방식으로 변경된다.&lt;/li&gt;
&lt;li&gt;애플리케이션과 파일 시스템 API를 동시에 설계하면 더 효율적이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="아키텍쳐"&gt;&lt;a href="#%ec%95%84%ed%82%a4%ed%85%8d%ec%b3%90" class="header-anchor"&gt;&lt;/a&gt;아키텍쳐
&lt;/h3&gt;&lt;p&gt;&lt;img
 src="https://blog.wlgh7407.com/images/Pasted%20image%2020260827132647.png"
 alt="Image Description"loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;아키텍쳐는 크게 마스터, 청크서버, 클라이언트 세가지로 이루어진다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기본적으로 모든 데이터들은 청크서버에 저장된다.
&lt;ul&gt;
&lt;li&gt;이 때 파일은 64MB의 청크 단위로 나눠져서 저장된다.&lt;/li&gt;
&lt;li&gt;각 청크는 여러 청크서버에 복제되며, 기본적으로 3개의 복제본을 가진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;마스터는 모든 파일 시스템의 메타데이터를 관리한다.
&lt;ul&gt;
&lt;li&gt;네임스페이스, 청크 위치 등의 저장은 물론&lt;/li&gt;
&lt;li&gt;청크 lease 관리, 가비지 컬렉션, HeartBeat등도 마스터가 수행함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;애플리케이션에 달려있는 클라이언트는 파일시스템 API가 달려있다.
&lt;ul&gt;
&lt;li&gt;POSIX API 없이 해당 아키텍쳐의 API로 파일을 가져올 수 있도록 구현되어있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="posix-vnode"&gt;&lt;a href="#posix-vnode" class="header-anchor"&gt;&lt;/a&gt;POSIX? vnode?
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;리눅스는 기본적으로 open(), read(), write(), close()와 같은 POSIX system call을 애플리케이션에게 제공한다.
&lt;ul&gt;
&lt;li&gt;실제 파일이 ext4(리눅스 기본 파일 시스템)에 있든, NFS(네트워크) 너머에 있든, USB 드라이브에 있든 모두 read() 하나로 읽을 수 있어야한다는 뜻&lt;/li&gt;
&lt;li&gt;그러기 위해서 리눅스 커널에는 VFS(Virtual File System) 레이어가 달려있고, 이 안에 vnode(virtual inode)라는 추상화계층이 있어서 이게 각 파일 시스템 안에서 동작하도록 해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;하지만 GFS는 익숙한 파일시스템 인터페이스를 지원하지만 (생성, 삭제, 열기, 닫기, 읽기, 쓰기 등)
&lt;ul&gt;
&lt;li&gt;클라이언트 입장에서 이는 라이브러리에서 함수를 호출하는 것과 같이 GFS 마스터와 청크서버와의 통신일뿐 직접 vnode계층에 연결하고 POSIX API를 구현할 필요가 없다!
&lt;ul&gt;
&lt;li&gt;물론 청크서버 안에서 해당 청크를 읽을때는 ext4를 읽듯이 읽겠지만.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="청크는-왜-64mb로"&gt;&lt;a href="#%ec%b2%ad%ed%81%ac%eb%8a%94-%ec%99%9c-64mb%eb%a1%9c" class="header-anchor"&gt;&lt;/a&gt;청크는 왜 64MB로?
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;2026년 현재는 64MB면 매우 작은 단위지만, 이 논문이 쓰여졌을 2003년으로 생각하면 64MB는 꽤나 큰 단위
&lt;ul&gt;
&lt;li&gt;따라서, &lt;strong&gt;lazy space allocation&lt;/strong&gt;이라는 개념을 통해 청크가 새로 생성될때 64MB를 바로 reserve하지 않음&lt;/li&gt;
&lt;li&gt;실제 데이터가 쓰여짐에 따라 필요한 만큼만 크기를 키우는 구조!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;청크 크기가 커야 마스터-클라이언트 상호작용 횟수가 줄어든다.
&lt;ul&gt;
&lt;li&gt;GFS의 구조에서는 모든 청크를 마스터가 관리하기때문에 꽤 중요한 이슈&lt;/li&gt;
&lt;li&gt;물론 작은 파일이 엄청 많이 들어온다면 여전히 곤란하긴 하다 (한 청크는 항상 한 파일에 속하기 때문)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그리고 이 청크를 크게 유지해야 마스터가 들고있을 메타데이터의 수가 적어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="파이프라인"&gt;&lt;a href="#%ed%8c%8c%ec%9d%b4%ed%94%84%eb%9d%bc%ec%9d%b8" class="header-anchor"&gt;&lt;/a&gt;파이프라인
&lt;/h3&gt;&lt;p&gt;읽는 방식은 알겠고, 그러면 쓰는 연산은 어떻게 진행되는지도 알아보자.&lt;/p&gt;
&lt;p&gt;&lt;img
 src="https://blog.wlgh7407.com/images/Pasted%20image%2020260827140049.png"
 alt="Image Description"loading="lazy"&gt;&lt;/p&gt;
&lt;h4 id="lease"&gt;&lt;a href="#lease" class="header-anchor"&gt;&lt;/a&gt;Lease
&lt;/h4&gt;&lt;p&gt;실제 순서에 앞서, &lt;strong&gt;Lease&lt;/strong&gt; 라는 개념에 대해 알아보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Write 혹은 Record Append의 연산을 mutation이라고 하자.&lt;/li&gt;
&lt;li&gt;mutation이 여러 복제본 간에 일관적인 순서를 유지할 수 있도록, 우리는 복제본 중 하나에 &lt;strong&gt;Chunk Lease&lt;/strong&gt;를 부여할 것이다.
&lt;ul&gt;
&lt;li&gt;이를 Primary라고 부르자.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Primary는 해당 청크에 대한 모든 mutation의 순서를 결정하고, 모든 복제본은 이 순서를 따른다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="write"&gt;&lt;a href="#write" class="header-anchor"&gt;&lt;/a&gt;Write
&lt;/h4&gt;&lt;p&gt;이제 Write 연산의 순서를 알아보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;클라이언트는 마스터에게 해당 청크의 Lease를 보유한 서버와 다른 복제본들의 위치를 요청한다.
&lt;ul&gt;
&lt;li&gt;만약 Lease를 보유한 서버가 없다면 마스터가 복제본 중 하나에 lease를 부여한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;마스터는 Primary와 다른 복제본(Secondary) 들의 위치를 응답한다.&lt;/li&gt;
&lt;li&gt;클라이언트는 모든 복제본에 데이터를 푸시한다. (위 그림에서 굵은 화살표, Data flow)
&lt;ul&gt;
&lt;li&gt;이 때 아직 데이터는 저장되는것이 아니라 캐시에 올려두기만 한다!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;모든 복제본이 데이터 수신을 확인하면 클라이언트는 Primary에 쓰기 요청을 보낸다.&lt;/li&gt;
&lt;li&gt;Primary는 모든 Secondary에 쓰기 요청을 전달한다.
&lt;ul&gt;
&lt;li&gt;위에서 말한대로 mutation 순서는 Primary가 제공한대로&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;모든 Secondary는 Primary에게 작업 완료 신호를 보낸다.&lt;/li&gt;
&lt;li&gt;Primary는 클라이언트에게 응답한다.
&lt;ul&gt;
&lt;li&gt;만약 어디서든 오류가 발생했다면 이는 클라이언트에 전달된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;핵심은 각 청크서버가 &lt;strong&gt;데이터를 수신한 타이밍과 쓰기연산을 진행하는 타이밍이 다르다는 것&lt;/strong&gt;
그래서 위 사진에서 Data flow와 Control flow가 구별되어서 그려져 있다!&lt;/p&gt;
&lt;h4 id="record-append"&gt;&lt;a href="#record-append" class="header-anchor"&gt;&lt;/a&gt;Record Append
&lt;/h4&gt;&lt;p&gt;GFS는 Record Append라는 연산도 제공한다.
기본 Write가 단일 Write라면, Append Record는 여러 producer가 동시에 같은 파일에 업데이트하는 경우&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;여러 서버가 동시에 하나의 파일에 로그를 남기는 상황같은거라고 이해하면 되겠따&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이런 상황에서는 기존 파일 시스템에서는 lock을 걸어서 동시 업데이트를 막는다던지 하는 방법이 있겠지만, GFS에서는 이를 &lt;strong&gt;정확한 / 같은 위치에 쓰기를 포기&lt;/strong&gt;하는 대신 &lt;strong&gt;lock 없는 동시 append&lt;/strong&gt;를 trade-off해서 얻어내었다.&lt;/p&gt;
&lt;p&gt;이를 위해 Write 연산과 약간의 차이가 있는데, Record Append에서는 쓰기 과정에서 어느 Secondary 하나가 쓰기에 실패하면 해당 시도에서 primary, 성공한 다른 Secondary에서는 반영되지만 Primary는 클라이언틍게 에러를 보고한다.
이후 클라이언트는 에러가 발생했으니 재시도하고, 이때 Primary나 이미 성공한 Secondary에서는 record가 두번 들어갈 수도 있다.
따라서 모든 복제본이 바이트 단위로 동일한 것은 보장되지 않고, 최소 한번 이상 기록되었음만 보장된다.&lt;/p&gt;
&lt;h4 id="snapshot"&gt;&lt;a href="#snapshot" class="header-anchor"&gt;&lt;/a&gt;Snapshot
&lt;/h4&gt;&lt;p&gt;현재 상황을 체크포인트로 저장하거나 브랜치 복사본을 생성하는 등의 Snapshot 연산에서는 copy-on-write라는 기능을 사용한다.&lt;/p&gt;
&lt;p&gt;이는 기존 파일이 &lt;code&gt;/data/foo = {청크 A1, A2, A3}&lt;/code&gt; 이었다면, 스냅샷을 만들때 &lt;code&gt;/snap/foo = {청크 A1, A2, A3}&lt;/code&gt; 과 같이 같은 청크를 가리키도록 포인터 레코드만 만드는 것이다!
이후 수정 요청이 들어오면 해당 청크만 새로 변화시켜서 &lt;code&gt;/data/foo = {청크 A1', A2, A3}&lt;/code&gt;처럼 될 수 있도록 바꾸는 것.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Persistent Segment Tree에서 본 철학과 비슷하다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="마스터-작업"&gt;&lt;a href="#%eb%a7%88%ec%8a%a4%ed%84%b0-%ec%9e%91%ec%97%85" class="header-anchor"&gt;&lt;/a&gt;마스터 작업
&lt;/h3&gt;&lt;p&gt;결국 저장을 제외한 모든 일들은 마스터가 진행해서 마스터가 할일이 많은데, 이를 나열하자면 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;namespace 관리 및 잠금
&lt;ul&gt;
&lt;li&gt;여러 작업 사이에 적절하게 lock을 걸기 / Lease를 잘 회수하기 등을 통해 버그를 방지함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;복제본 배치
&lt;ul&gt;
&lt;li&gt;청크 복제본을 여러 랙에 걸쳐 분산시키기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;생성, 재복제, 재균형화
&lt;ul&gt;
&lt;li&gt;복제본을 생성할때 디스크 공간 사용률이 낮은 서버에 배치할 수 있도록, 현재 쓰기 트래픽이 많이 않도록, 여러 랙에 분산될 수 있도록 함.&lt;/li&gt;
&lt;li&gt;청크서버가 죽는 등의 이유로 복제본의 수가 적어지면 즉시 청크를 다시 복제&lt;/li&gt;
&lt;li&gt;주기적으로 복제본을 이동시켜서 디스크 부하 분산 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가비지 컬렉션
&lt;ul&gt;
&lt;li&gt;파일이 삭제된 후 즉시 공간을 회수하지 않고 직접적인 연결을 끊는 방식(숨김파일 처리)을 수행&lt;/li&gt;
&lt;li&gt;이후 정기적인 스캔 과정에서 고립된 청크를 식별하면 해당 청크의 메타데이터를 삭제
&lt;ul&gt;
&lt;li&gt;이러면 다른 버그에서 발생한 필요없어진 청크와 함께 정리할 수 있는 이점이 있다!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;오래된 복제본 감지
&lt;ul&gt;
&lt;li&gt;청크서버에 장애가 발생해서 다운된동안 청크에 대해 변경 사항이 생기면 stale한 상태가 될 수 있다.&lt;/li&gt;
&lt;li&gt;이를 방지하기 위해 lease를 부여할 때마다 청크 버전 번호를 증가시키는 방식으로 버저닝을 하고, 그러면 어떤 청크의 버전 번호가 낮으면 이는 오래된 복제본이라고 가정할 수 있다.
&lt;ul&gt;
&lt;li&gt;그러면 위에서 배운대로 가비지 컬렉터가 정리해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="결함-허용-및-진단"&gt;&lt;a href="#%ea%b2%b0%ed%95%a8-%ed%97%88%ec%9a%a9-%eb%b0%8f-%ec%a7%84%eb%8b%a8" class="header-anchor"&gt;&lt;/a&gt;결함 허용 및 진단
&lt;/h3&gt;&lt;p&gt;우리들의 첫 가정이 &lt;strong&gt;컴포넌트의 장애는 일반적인 현상이다&lt;/strong&gt; 이기 때문에, 컴포넌트의 장애들에 대해서도 진단하고 해결할 방법을 정의해두어야 한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;빠른 복구
&lt;ul&gt;
&lt;li&gt;GFS는 구현된 프로세스 위에서 돌아가니까, 이를 프로세스단위로 죽이고 다시 시작하는게 가능하도록 설계함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;청크 복제
&lt;ul&gt;
&lt;li&gt;앞에서 정의한것과 같이 파일의 손상에 대비하기 위해 청크를 기본적으로 3개의 복제본으로 저장함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;마스터 복제
&lt;ul&gt;
&lt;li&gt;마스터가 모든 메타데이터를 관리하기때문에 마스터가 죽으면 이슈가 크다
&lt;ul&gt;
&lt;li&gt;따라서 신뢰성을 위해 마스터 상태는 복제됨&lt;/li&gt;
&lt;li&gt;이를 위해 마스터의 작업 로그와 체크포인트는 여러대의 머신에 복제되어 저장되고, mutation들은 해당 로그가 로컬은 물론 마스터 복제본의 디스크에도 flush 된 후에만 커밋된 것으로 간주됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그리고 마스터가 다운된 상태에서도 읽기 액세스를 제공할 수 있도록 shadow 마스터가 존재
&lt;ul&gt;
&lt;li&gt;어차피 파일은 청크서버에서 읽어오니까!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;체크섬
&lt;ul&gt;
&lt;li&gt;저장된 데이터의 손상을 감지하기 위해 청크를 64KB 블록으로 나누고, 각 블록에는 이에 대응하는 32비트 체크섬을 만들어서 로컬에 저장해둔다.
&lt;ul&gt;
&lt;li&gt;이후 읽기 연산때 기존 값과 같은지 검사하는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Record Append 연산에서와 같이 다른 청크서버간에 바이트코드가 그대로이지 않을 수 있다.
&lt;ul&gt;
&lt;li&gt;따라서 체크섬은 마스터가 아니라 청크서버 로컬에서 관리된다!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="정리"&gt;&lt;a href="#%ec%a0%95%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;정리
&lt;/h3&gt;&lt;p&gt;이렇게 만들어진 구글의 분산 파일 시스템인 GFS는 논문 공개 이후 아파치가 가져다가 오픈소스 프로젝트로 Hadoop을 만들었다.&lt;/p&gt;
&lt;p&gt;또, GFS가 완벽하지만은 않은게 작은 파일이 많이 들어오는 경우, 마스터 분산 불가능 등의 여러 문제가 있어 Google은 Colossus라는 파일시스템으로 발전했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;근데 Hadoop에 삐졌는지 이건 논문공개를 안했음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그래도 해당 개념 이후 BigTable, Jupiter, Dremel, Borg, BigQuery까지 넘어가면 재밌을거 같으니 하나하나 읽어보자 ㄱ.ㄱ&lt;/p&gt;
&lt;h2 id="질문-사항"&gt;&lt;a href="#%ec%a7%88%eb%ac%b8-%ec%82%ac%ed%95%ad" class="header-anchor"&gt;&lt;/a&gt;❔질문 사항
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="-참고-자료"&gt;&lt;a href="#-%ec%b0%b8%ea%b3%a0-%ec%9e%90%eb%a3%8c" class="header-anchor"&gt;&lt;/a&gt;🔗 참고 자료
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Ghemawat, S.; Gobioff, H.; Leung, S. T. (2003). &amp;ldquo;The Google file system&amp;rdquo;. &lt;a class="link" href="https://static.googleusercontent.com/media/research.google.com/en//archive/gfs-sosp2003.pdf" target="_blank" rel="noopener"
 &gt;&lt;em&gt;Proceedings of the nineteenth ACM Symposium on Operating Systems Principles - SOSP &amp;lsquo;03&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>