[컴퓨터 세상을 바꾼 25개 논문] GFS 와 BigTable
[컴퓨터 세상을 바꾼 25개 논문] GFS 와 BigTable
TL;DR
구글은 어떻게 수억 개의 웹페이지 정보를 저장하고, 수 밀리초 만에 검색 결과를 내놓을까요? 그 이면에는 “값싼 장비는 언제든 고장 날 수 있다”는 현실적인 인정에서 시작된 GFS와, 그 거대한 파일 시스템 위에서 데이터를 구조화한 BigTable이 있습니다. 현대의 NoSQL, 분산 데이터베이스, 그리고 클라우드 네이티브의 원형을 함께 살펴봅니다.
GFS: “실패를 일상으로 받아들인 파일 시스템”
우리가 서버 한두 대를 관리할 때는 ‘고장’이 특별한 이벤트지만, 수천 대의 서버를 운영하면 어떨까요? 어딘가의 하드디스크는 지금 이 순간에도 죽어가고 있을 것입니다. GFS는 바로 이 지점에서 시작합니다.
1. “서버가 죽는 것은 버그가 아니라 일상입니다”
수천 대의 서버가 항상 완벽하게 구동될 수 있을까요? 만약 한 대의 고장이 전체 시스템의 장애로 이어진다면 인프라 운영은 지옥이 될 것입니다. 전통적인 파일 시스템은 ‘안정적인 하드웨어’를 전제로 했기에 스케일 아웃에 취약했습니다.
구글은 이를 해결하기 위해 자동 복구 시스템을 핵심으로 설계했습니다. 중앙의 Master는 수많은 Chunkserver들과 끊임없이 통신(Heartbeat)하며 상태를 체크합니다. 만약 특정 서버가 응답하지 않으면, 그 서버가 가졌던 데이터를 즉시 다른 서버에 복제하도록 지시합니다. 마치 현대의 Kubernetes가 Pod의 상태를 감시하며 셀프 힐링을 수행하는 것과 같은 원리입니다.
2. “마스터가 병목이 된다면, 아예 일에서 빼버리세요”
모든 데이터 읽기/쓰기가 중앙 통제실(Master)을 거쳐야 한다면, 데이터가 커질수록 통제실은 마비될 것입니다. 백엔드 개발자라면 API 서버의 병목 현상을 해결하기 위해 Read/Write를 분리해 본 경험이 있을 것입니다.
GFS는 이를 위해 제어부(Control Plane)와 데이터부(Data Plane)를 완전히 분리했습니다. 클라이언트는 마스터에게 “내가 찾는 파일이 어디 있니?”라고 위치만 물어본 뒤, 실제 수 기가바이트의 데이터 전송은 해당 Chunkserver와 직접 수행합니다. 마스터는 길 안내만 해줄 뿐, 무거운 짐은 옮기지 않는 구조를 통해 병목 현상을 혁신적으로 제거했습니다.
3. “수조 개의 작은 파일보다는, 덩어리를 크게 키우는 게 이득입니다”
파일 시스템의 인덱스 정보(메타데이터)가 너무 많아지면 마스터의 메모리가 버티지 못합니다. 구글은 일반적인 OS가 4KB 단위를 쓸 때, 과감하게 64MB라는 거대한 Chunk 단위를 선택했습니다.
단위가 커지면 관리해야 할 인덱스 개수가 획기적으로 줄어들어 마스터의 메모리 부담이 줄어들고, 클라이언트는 한 번의 네트워크 통신으로 훨씬 많은 데이터를 읽어올 수 있게 됩니다. “네트워크 왕복(Round-trip)을 줄이는 것이 성능의 핵심”이라는 백엔드의 진리를 시스템 설계에 그대로 녹여낸 것입니다.
4. “데이터 정합성? ‘Append-only’로 복잡함을 도려내다”
여러 클라이언트가 동시에 같은 파일의 같은 위치를 수정(Overwrite)하려고 하면 어떤 일이 벌어질까요? 분산 환경에서의 분산 락(Distributed Lock)은 성능을 갉아먹는 주범입니다.
구글은 이 문제를 Atomic Record Append라는 방식으로 우회했습니다. 기존 데이터를 수정하는 대신, 파일 끝에 데이터를 추가하는 동작만 보장하는 것입니다. 데이터가 중복되거나 순서가 조금 바뀔 수는 있지만, “최소 한 번은 기록된다(At-least-once)”는 보장을 통해 복잡한 동기화 비용을 획기적으로 낮췄습니다.
BigTable: “거대한 데이터를 다루는 거대한 맵(Map)”
GFS가 거대한 저장소라면, 그 안에서 원하는 데이터를 빛의 속도로 찾아내고 구조화하는 역할은 BigTable이 담당합니다. 우리가 흔히 쓰는 RDBMS로는 감당할 수 없는 스케일을 어떻게 풀어냈을까요?
1. “관계(Relation)를 버리고 확장성(Scalability)을 얻다”
수조 행의 데이터를 가진 테이블에서 JOIN을 수행하거나 트랜잭션을 관리하는 것은 분산 환경에서 거의 불가능에 가깝습니다.
BigTable은 과감하게 복잡한 관계를 포기하고, 본질적으로는 (Row, Column, Timestamp)라는 세 가지 키로 값을 찾는 거대한 Sorted Map 구조를 선택했습니다. 데이터는 Row Key를 기준으로 사전순 정렬되어 저장되는데, 덕분에 특정 범위의 데이터를 읽는 ‘Range Scan’ 성능이 압도적입니다. 이는 현대 NoSQL의 표준 모델이 되었습니다.
2. “느린 디스크 쓰기, 메모리를 거쳐 순차 쓰기로 바꿉니다”
디스크의 이곳저곳을 찾아다니며 데이터를 쓰는 ‘Random Write’는 매우 느립니다. 데이터가 쏟아지는 구글의 환경에서는 치명적이죠.
BigTable은 LSM-Tree(Log-Structured Merge-Tree) 구조를 사용해 이를 해결합니다.
모든 쓰기 요청은 일단 메모리(Memtable)에만 기록하고 끝냅니다. “방금 쓴 데이터가 날아가면 어쩌지?”라는 걱정은 GFS에 순차적으로 기록하는 로그(Commit Log)가 해결해 줍니다. 메모리가 가득 차면 그 내용을 한꺼번에 디스크에 파일(SSTable)로 툭 던집니다. 디스크 헤더가 움직일 필요 없는 순차 쓰기(Sequential Write) 덕분에 엄청난 쓰기 처리량을 확보하게 됩니다.
3. “지저분해진 파일들은 백그라운드에서 정리하면 됩니다”
데이터가 계속 쌓이면 디스크에는 수많은 SSTable 파일들이 생겨납니다. 읽기 요청이 들어왔을 때 이 파일들을 다 뒤져야 한다면 성능이 떨어지겠죠.
여기서 Compaction(압착) 개념이 등장합니다. 시스템이 한가한 시간에 백그라운드에서 작은 파일들을 합쳐서 큰 파일로 만들고, 삭제된 데이터들을 실제로 지우는 정리를 수행합니다. 또한, Bloom Filter라는 알고리즘을 사용해 “이 파일에 네가 찾는 데이터가 아마 없을 거야”라고 미리 알려줌으로써 불필요한 디스크 조회를 획기적으로 줄였습니다.
4. “누가 이 모든 것을 지휘하나? Chubby와 Tablet”
테이블이 너무 커지면 Row 범위를 기준으로 Tablet이라는 단위로 쪼개어 여러 서버에 분산합니다. 이때 “어떤 서버가 살아있는지”, “어떤 서버가 어떤 데이터를 가졌는지”에 대한 합의가 필요합니다.
BigTable은 이를 위해 Chubby라는 분산 락 서비스를 사용합니다. 이는 분산 시스템의 ‘Single Source of Truth’ 역할을 하며, 시스템 전체의 일관성을 유지하는 닻 역할을 합니다.
주니어 개발자에게: 거인의 어깨 위에서 바라보기
현대적인 프레임워크와 최신 문법을 익히는 것도 중요하지만, 가끔은 이런 고전 논문을 통해 왜 이런 구조가 탄생했는가라는 근본적인 질문을 던져보시길 권합니다. GFS와 BigTable이 해결하고자 했던 ‘확장성’과 ‘가용성’에 대한 고민은 오늘날 우리가 사용하는 Kafka, Cassandra, 심지어 클라우드 네이티브 아키텍처에도 고스란히 녹아 있습니다. 당연하다고 생각했던 것들을 돌아보는 순간, 코드를 바라보는 여러분의 시야는 한 단계 더 깊어질 것입니다.




