<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Paper_Review on Jiho Kim</title><link>https://blog.wlgh7407.com/tags/paper_review/</link><description>Recent content in Paper_Review on Jiho Kim</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Fri, 28 Aug 2026 15:18:47 +0900</lastBuildDate><atom:link href="https://blog.wlgh7407.com/tags/paper_review/index.xml" rel="self" type="application/rss+xml"/><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>