- 들어가며 — 한 번 배우면 어디서나 보이는 패턴
- 위치 주소 vs 내용 주소
- 공짜로 따라오는 세 가지 — 중복 제거, 무결성, 캐싱
- 불변성 — 왜 자연스럽게 따라오는가
- 머클 트리와 DAG — 해시를 엮어 구조를 만들다
- 현실의 사례들 — 같은 아이디어의 다섯 얼굴
- 그늘 — 콘텐츠 주소 지정의 한계
- 마치며 — 하나를 알면 열이 보인다
- 참고 자료
들어가며 — 한 번 배우면 어디서나 보이는 패턴
시스템을 오래 다루다 보면 이상한 경험을 합니다. 전혀 다른 도구들을 배웠는데, 어느 순간 그것들이 사실 같은 아이디어의 변주였음을 깨닫는 순간입니다. 콘텐츠 주소 지정 저장소(content-addressable storage, CAS)가 바로 그런 아이디어입니다.
Git이 어떻게 커밋을 저장하는지, Docker가 어떻게 이미지 레이어를 공유하는지, IPFS가 어떻게 파일을 분산하는지, Nix가 어떻게 패키지를 격리하는지, 비트토렌트가 어떻게 조각을 검증하는지. 이 다섯 가지는 서로 무관해 보이지만, 그 밑바닥에는 완전히 동일한 발상이 깔려 있습니다. 데이터를 그것이 어디 있는지가 아니라, 그것이 무엇인지로 주소 지정한다.
이 글은 이 하나의 아이디어를 파고듭니다. 왜 위치 대신 내용으로 주소를 매기는 것이 중복 제거, 무결성, 캐싱을 공짜로 주는지, 왜 불변성이 자연스럽게 따라오는지, 그리고 이 발상이 머클 트리와 DAG로 어떻게 확장되어 오늘날의 인프라를 떠받치는지를 봅니다.
위치 주소 vs 내용 주소
우리가 데이터를 다루는 전통적인 방식은 위치 주소 지정(location-addressing)입니다. 파일은 /home/user/report.pdf 같은 경로에 있고, 웹 리소스는 https://example.com/logo.png 같은 URL에 있습니다. 여기서 주소는 "어디에 있는가"를 가리킵니다. 주소를 알면 그곳에 가서 무엇이 있든 가져옵니다.
이 방식의 근본적 특성은 주소와 내용이 분리되어 있다는 것입니다. report.pdf의 내용을 통째로 바꿔도 경로는 그대로입니다. 같은 URL이 오늘과 내일 다른 내용을 줄 수도 있습니다. 주소는 그릇의 이름일 뿐, 그 안에 무엇이 담겼는지는 보장하지 않습니다.
콘텐츠 주소 지정은 이 관계를 뒤집습니다. 주소를 내용으로부터 계산합니다. 구체적으로는 데이터에 암호학적 해시 함수(SHA-256 같은)를 돌려 나온 해시값을 그 데이터의 주소로 씁니다.
위치 주소:
"이 데이터는 /path/to/file 에 있다"
(주소가 위치를 가리킴 — 내용은 바뀔 수 있음)
내용 주소:
"이 데이터의 주소는 hash(데이터) 이다"
(주소가 곧 내용의 지문 — 내용이 바뀌면 주소도 바뀜)
이 작은 뒤집기에서 놀라운 성질들이 줄줄이 따라 나옵니다. 해시가 어떤 것인지 잠깐 짚고 가면, 해시 함수는 임의 길이의 데이터를 받아 고정 길이의 짧은 값으로 뭉갭니다. 좋은 암호학적 해시는 두 가지 결정적 성질을 가집니다. 같은 입력은 항상 같은 출력을 내고(결정론적), 서로 다른 두 입력이 같은 출력을 내는 경우(충돌)를 찾기가 사실상 불가능합니다. 이 두 성질이 CAS의 모든 마법을 떠받칩니다. 직접 해시를 계산해 보고 싶다면 해시 생성기에서 같은 입력이 항상 같은 해시를 내는 것을, 한 글자만 바꿔도 해시가 완전히 달라지는 것을 눈으로 확인할 수 있습니다.
공짜로 따라오는 세 가지 — 중복 제거, 무결성, 캐싱
콘텐츠 주소 지정의 진짜 매력은, 주소를 내용의 해시로 정하는 순간 세 가지 유용한 성질이 저절로 딸려 온다는 것입니다. 따로 구현할 필요 없이, 구조 자체에서 나옵니다.
중복 제거(deduplication)가 공짜다. 두 데이터가 완전히 같으면 해시도 같습니다. 즉 같은 내용은 같은 주소를 가집니다. 그러므로 저장소에 이미 그 주소가 있으면, 다시 저장할 필요가 없습니다. 같은 파일을 백 번 저장하려 해도 실제로는 한 번만 저장됩니다. 중복 제거를 위한 별도의 비교 로직이 필요 없습니다. 주소가 같은지만 보면 됩니다.
무결성(integrity) 검증이 공짜다. 데이터를 받았을 때, 그 데이터를 다시 해시해서 요청했던 주소와 일치하는지만 보면 됩니다. 일치하면 그 데이터는 틀림없이 원본입니다. 한 비트라도 변조되었다면 해시가 달라져 주소와 맞지 않습니다. 별도의 체크섬 필드나 서명 없이, 주소 그 자체가 무결성 증명입니다. 이것이 비트토렌트가 신뢰할 수 없는 낯선 피어로부터 조각을 받고도 안전한 이유입니다.
캐싱(caching)이 공짜다. 콘텐츠 주소는 절대 다른 내용을 가리키지 않습니다. hash(X)라는 주소는 영원히 X만을 의미합니다. 그러므로 한 번 받은 것은 무한히 캐시해도 됩니다. 캐시가 낡을 걱정이 없습니다. 위치 주소에서 골치 아픈 "캐시 무효화" 문제가 여기서는 아예 발생하지 않습니다. 주소가 곧 내용이라, 주소가 같으면 내용도 반드시 같기 때문입니다.
이 세 가지가 "공짜"라는 것을 강조하는 이유가 있습니다. 위치 주소 시스템에서 중복 제거, 무결성, 캐시 무효화는 각각 복잡한 엔지니어링을 요구하는 어려운 문제입니다. 콘텐츠 주소 지정은 이 문제들을 풀지 않습니다. 아예 존재하지 않게 만듭니다.
불변성 — 왜 자연스럽게 따라오는가
콘텐츠 주소 지정에서 따라오는 또 하나의 근본 성질이 불변성(immutability)입니다. 콘텐츠 주소 저장소의 객체는 절대 제자리에서 수정되지 않습니다. 수정될 수가 없습니다.
이유는 단순합니다. 데이터를 바꾸면 해시가 바뀌고, 해시가 바뀌면 그것은 이미 다른 주소를 가진 다른 객체이기 때문입니다. hash(X)에 저장된 것을 "수정"한다는 개념 자체가 성립하지 않습니다. X를 Y로 바꾸면, 그것은 hash(X)의 수정이 아니라 hash(Y)라는 새 객체의 생성입니다. 옛 객체 hash(X)는 그대로 남아 있습니다.
이 불변성이 실무에서 갖는 함의는 큽니다.
- 동시성이 쉬워진다: 객체가 절대 바뀌지 않으므로, 여러 프로세스가 동시에 읽어도 락이 필요 없습니다. 읽는 도중에 내용이 바뀔 일이 없기 때문입니다.
- 버전 관리가 자연스럽다: 새 버전은 옛 버전을 덮어쓰지 않고 새 주소로 추가됩니다. 모든 버전이 각자의 주소로 공존합니다. Git의 역사가 바로 이렇게 쌓입니다.
- 참조가 견고하다: 어떤 주소를 가리키는 참조는, 그 대상이 변조되지 않았음을 보장받습니다. 주소가 곧 내용의 지문이기 때문입니다.
"변경"을 표현하고 싶으면 어떻게 할까요? 불변 객체를 수정하는 대신, 새 객체를 만들고 그것을 가리키는 포인터(가변적인 이름표)를 옮깁니다. Git에서 브랜치가 하는 일이 정확히 이것입니다. 커밋 객체 자체는 불변이지만, main이라는 브랜치 이름은 가변 포인터로서 최신 커밋을 가리키다가, 새 커밋이 생기면 그리로 옮겨갑니다. 불변 데이터 위에 가변 포인터를 얹는 이 패턴은 CAS 시스템 어디서나 반복됩니다.
머클 트리와 DAG — 해시를 엮어 구조를 만들다
지금까지는 낱개 데이터 하나에 주소를 매기는 이야기였습니다. 하지만 실제 시스템은 파일 하나가 아니라 디렉터리 트리, 커밋 역사, 레이어 스택 같은 복잡한 구조를 다룹니다. 콘텐츠 주소 지정을 이런 구조로 확장하는 것이 머클 트리(Merkle tree)와 머클 DAG입니다.
핵심 아이디어는 이렇습니다. 어떤 객체가 다른 객체들을 참조할 때, 그 참조를 다름 아닌 자식들의 해시로 표현합니다. 그러면 부모 객체의 내용 안에 자식들의 해시가 들어가고, 따라서 부모의 해시는 자식들의 해시에 의존합니다.
루트 해시 (전체를 대표하는 하나의 지문)
|
+--+--+
| |
해시A 해시B <- 이 두 해시가 루트의 내용에 포함됨
| |
데이터 데이터
이 구조에서 나오는 성질이 결정적입니다. 아래쪽 데이터가 한 조각이라도 바뀌면, 그 조각의 해시가 바뀌고, 그것을 참조하는 부모의 해시가 바뀌고, 그 위 부모의 해시가 바뀌어, 결국 루트 해시까지 바뀝니다. 반대로 말하면, 루트 해시 하나가 같다는 것은 그 아래 전체 트리가 마지막 바이트까지 동일함을 보장합니다.
이것이 왜 강력한지 봅시다.
- 거대한 구조를 하나의 해시로 요약: 수백만 개 파일로 이루어진 전체 상태를, 루트 해시 하나로 지문화합니다. 두 시스템의 루트 해시가 같으면 전체가 같습니다. 이 한 번의 비교로 방대한 검증을 대신합니다.
- 부분 검증과 부분 전송: 트리 전체를 받지 않아도, 특정 조각과 거기까지의 해시 경로만으로 그 조각의 진위를 증명할 수 있습니다. 비트토렌트가 파일 전체를 받기 전에 조각별로 검증하는 원리입니다.
- 효율적 차이 계산: 두 트리에서 루트가 다르면 자식으로 내려가며 다른 가지만 추적합니다. 같은 해시를 가진 가지는 통째로 건너뜁니다. Git이 방대한 역사에서도 빠르게 변경분을 찾는 비결입니다.
머클 DAG는 여기서 한 걸음 더 나갑니다. 트리는 각 노드가 부모 하나를 갖지만, DAG(방향성 비순환 그래프)에서는 여러 부모가 같은 자식을 공유할 수 있습니다. 이것이 중복 제거와 결합하면 강력합니다. 여러 커밋이나 여러 이미지가 동일한 하위 객체를 참조할 때, 그 객체는 딱 한 번만 저장되고 모두가 그 해시로 공유합니다.
현실의 사례들 — 같은 아이디어의 다섯 얼굴
이제 이 아이디어가 실제 시스템에서 어떻게 나타나는지 봅시다. 놀라운 것은 표면이 전혀 다른데도 뼈대가 똑같다는 점입니다.
Git. Git은 콘텐츠 주소 저장소의 교과서적 예입니다. Git의 모든 것(파일 내용은 blob, 디렉터리는 tree, 스냅숏은 commit)이 그 내용의 해시로 주소 지정된 객체입니다. 같은 파일이 여러 커밋에 등장해도 blob은 한 번만 저장됩니다(중복 제거). 커밋 해시는 그 커밋의 전체 내용과 부모 커밋에 의존하므로, 역사의 어느 한 지점을 조작하면 그 이후 모든 해시가 달라집니다(무결성). Git의 커밋 그래프는 그야말로 머클 DAG입니다. Git 객체가 어떻게 쌓이는지 브라우저에서 직접 실습해 보고 싶다면 Git 실습장이 좋은 출발점입니다.
Docker와 OCI 이미지. 컨테이너 이미지는 레이어의 스택이고, 각 레이어는 그 내용의 해시(digest)로 식별됩니다. 두 이미지가 같은 베이스 레이어를 쓰면, 그 레이어는 디스크에도 레지스트리에도 한 번만 저장되고 공유됩니다(중복 제거). docker pull이 이미 있는 레이어를 건너뛰는 것도 이 때문입니다. 다운로드 후 digest를 재계산해 무결성을 검증하고, digest가 같으면 다시 받지 않습니다(캐싱). 이미지 전체를 대표하는 매니페스트 역시 레이어 digest들을 엮은 콘텐츠 주소 구조입니다.
IPFS. IPFS는 콘텐츠 주소 지정을 웹 규모로 밀어붙인 분산 파일 시스템입니다. 파일은 위치가 아니라 CID(Content Identifier)로 주소 지정됩니다. CID는 콘텐츠의 해시를 담고 있어서, 어떤 노드에서 받든 CID로 무결성을 검증할 수 있습니다. 큰 파일은 청크로 쪼개져 머클 DAG로 엮이므로, 부분 전송과 중복 제거가 자연스럽게 됩니다. "이 CID를 달라"고 네트워크에 물으면, 그것을 가진 아무 노드나 응답할 수 있습니다. 주소가 위치와 분리되었기 때문입니다.
Nix. Nix 패키지 매니저는 각 빌드 결과물을 그 빌드 입력 전체의 해시로 주소 지정해 /nix/store 아래 격리합니다. 같은 입력은 같은 경로를 내므로, 같은 패키지가 여러 번 빌드되지 않습니다. 다른 입력은 다른 경로를 내므로, 서로 다른 버전이 충돌 없이 공존합니다. 이것이 Nix가 재현 가능한 빌드와 원자적 롤백을 이루는 핵심입니다. 해시가 곧 격리 경계인 셈입니다.
비트토렌트. 비트토렌트는 파일을 조각(piece)으로 나누고, 각 조각의 해시를 토렌트 메타데이터에 담습니다. 다운로더는 신뢰할 수 없는 낯선 피어들로부터 조각을 받지만, 각 조각을 해시로 검증하기 때문에 손상되거나 악의적인 조각을 즉시 걸러냅니다. 누구에게서 받든 상관없습니다. 조각의 해시가 맞으면 진짜이기 때문입니다. 콘텐츠 주소 지정이 제로 트러스트 분산 전송을 가능하게 하는 전형입니다.
이 다섯을 나란히 놓으면 패턴이 선명해집니다. 버전 관리, 컨테이너, 분산 파일 시스템, 패키지 관리, P2P 전송 — 완전히 다른 문제 영역인데 해법의 뼈대가 하나입니다. 데이터를 내용으로 주소 지정하고, 그것들을 해시로 엮어 구조를 만든다.
그늘 — 콘텐츠 주소 지정의 한계
이 아이디어가 강력하다고 해서 만능은 아닙니다. 콘텐츠 주소 지정에는 분명한 대가와 한계가 있습니다. 도구를 설계할 때 이것들을 알아야 합니다.
- 가변 데이터에 부적합: 콘텐츠 주소는 본질적으로 불변 데이터를 위한 것입니다. 자주 바뀌는 데이터는 바뀔 때마다 새 주소를 낳으므로, "이 이름의 최신 버전"을 가리키려면 별도의 가변 포인터 계층(이름 → 현재 해시)이 필요합니다. Git의 브랜치, IPFS의 IPNS가 이 계층입니다. 즉 콘텐츠 주소만으로는 "변경"을 표현할 수 없고, 위에 이름 계층을 얹어야 합니다.
- 가비지 컬렉션 문제: 불변 객체는 계속 쌓입니다. 아무도 참조하지 않게 된 객체를 언제 지울지가 골칫거리입니다. 어떤 가변 포인터도 도달할 수 없는 객체를 찾아 수거하는 GC가 필요하고, 이는 만만치 않은 작업입니다. Git의 저장소가 시간이 지나며 비대해지는 것도 이 때문입니다.
- 해시 계산 비용: 모든 데이터에 암호학적 해시를 돌리는 것은 공짜가 아닙니다. 대용량 데이터에서는 해시 계산 자체가 부담이 될 수 있습니다. 다만 현대 하드웨어에서는 대개 감내할 만한 수준입니다.
- 충돌과 해시 함수 노후화: 보안은 해시 함수의 충돌 저항성에 의존합니다. 과거 널리 쓰이던 해시 함수들이 시간이 지나 취약해진 전례가 있습니다. 콘텐츠 주소 시스템은 해시 함수가 깨지면 무결성 보장이 무너지므로, 더 강한 해시로 이전하는 것은 대규모 시스템에서 대단히 어려운 과제입니다. 이미 수억 개의 주소가 옛 해시로 박혀 있기 때문입니다.
- 위치의 실종: 주소가 위치를 담지 않으므로, "이 데이터를 어디서 구하지"는 별도로 풀어야 합니다. IPFS가 DHT(분산 해시 테이블)로 "이 CID를 누가 갖고 있나"를 찾는 계층을 따로 두는 이유입니다. 내용 주소는 무엇인지는 알려주지만 어디 있는지는 알려주지 않습니다.
이 한계들을 종합하면, 콘텐츠 주소 지정은 "불변이고, 무결성이 중요하며, 중복이 많고, 널리 캐시·공유되는 데이터"에 최적입니다. 자주 바뀌는 상태나, 위치가 본질적으로 중요한 데이터에는 위치 주소 위에 콘텐츠 주소를 부분적으로 얹는 하이브리드가 현실적입니다. 실제로 위에서 본 모든 시스템이 콘텐츠 주소(불변 객체) 위에 가변 이름 계층을 얹은 하이브리드입니다.
마치며 — 하나를 알면 열이 보인다
콘텐츠 주소 지정은 겉으로는 소박한 아이디어입니다. "데이터를 그 해시로 주소 지정하라." 하지만 이 한 문장에서 중복 제거, 무결성, 캐싱, 불변성이 줄줄이 딸려 나오고, 머클 트리로 확장되면 거대한 구조를 하나의 해시로 검증하는 능력까지 얻습니다. 위치가 아니라 내용으로 주소를 매긴다는 관점의 전환 하나가, 이렇게 많은 어려운 문제를 애초에 존재하지 않게 만듭니다.
이 아이디어를 한 번 내면화하면, 그다음부터는 어디서나 보입니다. 새로운 도구를 배울 때 "이거 혹시 콘텐츠 주소 지정 아닌가"를 물어보면, 의외로 자주 그렇습니다. Git, Docker, IPFS, Nix, 비트토렌트가 그랬듯이, 콘텐츠 주소 지정 블록 저장소, 콘텐츠 주소 지정 캐시, 콘텐츠 주소 지정 아티팩트 저장소가 계속 등장합니다. 서로 다른 이름표를 달고 있지만 뼈대는 같습니다.
시스템을 깊이 이해한다는 것은 결국 이런 반복되는 아이디어를 알아보는 능력입니다. 표면의 차이 아래 흐르는 공통의 발상을 보면, 새 도구를 배우는 일이 매번 처음부터 시작하는 것이 아니라, 이미 아는 패턴의 또 다른 얼굴을 만나는 일이 됩니다. 콘텐츠 주소 지정은 그런 패턴 중에서도 가장 우아하고 널리 퍼진 것 중 하나입니다.
참고 자료
- Git 내부 구조 (Pro Git): https://git-scm.com/book/en/v2/Git-Internals-Git-Objects
- OCI 이미지 명세: https://github.com/opencontainers/image-spec
- IPFS 문서 (콘텐츠 주소 지정): https://docs.ipfs.tech/concepts/content-addressing/
- Nix 스토어: https://nixos.org/guides/nix-pills/
- 머클 트리 (Wikipedia): https://en.wikipedia.org/wiki/Merkle_tree
- 비트토렌트 명세 (BEP 3): https://www.bittorrent.org/beps/bep_0003.html
현재 단락 (1/63)
시스템을 오래 다루다 보면 이상한 경험을 합니다. 전혀 다른 도구들을 배웠는데, 어느 순간 그것들이 사실 같은 아이디어의 변주였음을 깨닫는 순간입니다. 콘텐츠 주소 지정 저장소(c...