
Hard Link vs Symbolic Link: 바로가기 만들다 맥북 날릴 뻔한 썰
파일 복사(Copy)와 링크(Link)의 차이를 몰라 디스크 용량을 2배로 낭비했던 흑역사. Inode의 개념부터 `rm` 명령어의 비밀, 그리고 npm과 pnpm이 링크를 활용하는 방법까지.

파일 복사(Copy)와 링크(Link)의 차이를 몰라 디스크 용량을 2배로 낭비했던 흑역사. Inode의 개념부터 `rm` 명령어의 비밀, 그리고 npm과 pnpm이 링크를 활용하는 방법까지.
내 서버는 왜 걸핏하면 뻗을까? OS가 한정된 메모리를 쪼개 쓰는 처절한 사투. 단편화(Fragmentation)와의 전쟁.

운영체제라는게 사실 프로그램들의 집합이라면, 그 중에서도 가장 핵심이 되는 녀석은 누구일까요? 항상 메모리에 상주하는 커널의 정체.

CPU는 하나인데 프로그램은 수십 개 실행됩니다. 운영체제가 0.01초 단위로 프로세스를 교체하며 '동시 실행'의 환상을 만드는 마술. 선점형 vs 비선점형, 기아 현상(Starvation), 그리고 현대적 해법인 MLFQ를 파헤칩니다.

가상 머신들의 조율자. 사장님이 직접 운영하느냐(Type 1), 매니저를 두느냐(Type 2)의 차이.

개발자 초년생 시절, 저는 머신러닝 데이터셋을 관리하고 있었습니다.
300GB짜리 이미지 데이터셋(dataset_v1)이 있었는데, 실험을 위해 폴더 구조를 조금 바꿔야 했습니다.
원본을 건드리기 무서워서 복사(Copy)를 했습니다 (cp -r dataset_v1 dataset_v2).
순식간에 제 맥북의 디스크 용량은 바닥났고, "Disk Full" 경고와 함께 빌드가 멈췄습니다. 선배가 지나가면서 한마디 툭 던지더군요. "야, 그걸 통째로 복사하면 어떡해? 심볼릭 링크(Symbolic Link) 걸어야지."
"네? 윈도우 바로가기 같은 건가요?" "비슷한데 달라. 아, 그리고 데이터 중요하면 하드 링크(Hard Link) 써야 할 수도 있고."
그날 저는 ln -s 명령어를 처음 배웠고, 파일 시스템의 심연인 Inode의 세계로 입문했습니다.
오늘은 저처럼 무지성 복사 붙여넣기로 디스크를 괴롭히는 분들을 위해, 리눅스 파일 시스템의 비밀을 파헤쳐 봅니다.
우리는 파일 탐색기에서 파일 아이콘을 보면 "아, 저게 파일이구나"라고 생각합니다. 하지만 운영체제(Linux/Unix/macOS) 입장에서 파일은 두 부분으로 나뉩니다.
report.txt라는 이름은 단지 특정 Inode 번호를 가리키는 포인터(링크)일 뿐입니다.충격적 사실: 우리가 파일을 지우는
rm file.txt명령어는, 사실 파일 데이터를 지우는 게 아닙니다. 단지 파일 이름(껍데기)과 Inode(실체)의 연결을 끊는 것(Unlink)입니다. 연결된 이름이 0개가 되면(Reference Count = 0), 그때 비로소 OS가 "아, 이 Inode는 아무도 안 쓰는구나" 하고 디스크 공간을 회수합니다.
ln target.txt hardlink.txttarget.txt와 hardlink.txt는 완전히 동일한 파일입니다. (Inode 번호가 같음)ls -i 명령어로 확인해보면 Inode 번호가 똑같이 나옵니다.하드 링크 파일 중 원본(target.txt)을 지워도 hardlink.txt는 멀쩡히 살아있습니다.
데이터가 복사된 게 아닌데 어떻게 가능할까요?
Inode에는 Link Count라는 숫자가 있습니다. 하드 링크를 만들면 이 숫자가 1에서 2로 올라갑니다.
rm target.txt를 하면 카운트가 2 -> 1로 줄어들 뿐, 0이 아니므로 데이터는 유지됩니다.
마지막 rm hardlink.txt를 해야 카운트가 0이 되어 진짜로 삭제됩니다.
ln -s target.txt symlink.txtC:\Program Files\LoL\LeagueClient.exe를 실행해 줍니다.symlink.txt는 자기만의 새로운 Inode를 가집니다. (원본과 다름).target.txt)을 지우면? 심볼릭 링크는 "깨진 링크(Broken Link)"가 됩니다. 클릭해도 No such file 에러가 뜹니다./usr/bin/python -> python3.9
python3.10을 가리키도록 갱신하면 됩니다./usr/bin/python만 호출하면 되므로, 코드 수정 없이 버전을 갈아끼울 수 있습니다..zshrc, .vimrc)은 실제로는 Dropbox 폴더에 있고, 홈 디렉토리(~/)에는 심볼릭 링크만 둡니다.하드 링크가 더 안전해 보이고(원본 지워도 살아남음) 빠르지만, 치명적인 단점 2가지 때문에 평소엔 잘 안 씁니다.
find 같은 탐색 프로그램이 영원히 돌다가 뻗어버립니다.C:)의 Inode 100번과 USB 드라이브(D:)의 Inode 100번은 전혀 다른 겁니다.반면 심볼릭 링크는:
Node.js 개발자라면 node_modules 폴더가 블랙홀처럼 무겁다는 걸 알 겁니다. 왜 그럴까요?
A 프로젝트와 B 프로젝트가 둘 다 React v18을 쓴다면?
npm은 각각의 node_modules 폴더에 React 파일을 물리적으로 따로 저장(복사)합니다.
프로젝트가 100개면 React도 100번 저장됩니다. 디스크 용량 낭비가 심하고, 설치 속도(I/O)도 느립니다.
pnpm(Performant NPM)은 하드 링크를 적극적으로 활용합니다.
~/.pnpm-store라는 글로벌 저장소에 딱 한 번만 다운로드합니다.node_modules에는 그 저장소 파일을 가리키는 하드 링크를 생성합니다.결과:
이 원리를 알고 난 뒤, 저는 모든 프로젝트를 npm에서 pnpm으로 마이그레이션했습니다. 맥북 용량이 50GB는 늘어났습니다.
하드 링크는 Docker 이미지 레이어와 CI/CD 캐싱에서도 핵심적인 역할을 합니다.
Docker 이미지는 여러 레이어(Layer)로 구성됩니다.
COPY . . 명령어를 실행할 때, Docker는 변경된 파일만 새로운 레이어로 만듭니다.
하지만 내부적으로는 OverlayFS 같은 Union File System을 사용하는데, 이들은 하드 링크와 유사한 메커니즘으로 동일한 데이터를 공유합니다.
만약 모든 레이어를 매번 복사했다면, Docker 이미지는 수십 GB가 되었을 겁니다.
GitHub Actions의 actions/cache도 비슷합니다.
node_modules를 캐시에서 복원할 때, 단순히 압축을 푸는 것보다 하드 링크를 활용하면 복원 속도가 비약적으로 빨라집니다.
특히 Monorepo 환경(Turborepo, Nx)에서는 패키지 간의 의존성을 하드 링크로 연결하여 빌드 시간을 단축시킵니다.
| 특징 | Hard Link (하드 링크) | Symbolic Link (심볼릭 링크) |
|---|---|---|
| 정체 | Inode에 붙은 여분의 이름표 | 원본 주소를 적어둔 별도의 파일 |
| Inode 번호 | 원본과 같음 | 원본과 다름 (새 파일) |
| 원본 삭제 시 | 파일은 살아있음 (데이터 유지) | 링크 파일은 깨짐 (데드 링크) |
| 대상 | 파일만 가능 (디렉토리 불가) | 파일 & 디렉토리 모두 가능 |
| 범위 | 같은 드라이브(파티션) 내에서만 | 다른 드라이브/네트워크 가능 |
| 비유 | 예명 (본캐/부캐 둘 다 나임) | 바로가기 아이콘 |
rm -rf /의 공포를 넘어서이제 rm 명령어가 덜 무섭습니다.
"아, 내가 지우는 건 파일 데이터가 아니라, 단지 연결(Link)을 끊는 거구나."
하지만 역설적으로 rm -rf /는 더 무섭게 다가옵니다.
이건 모든 디렉토리의 연결 고리를 끊어서, OS가 "어? 이 파일들 아무도 안 쓰네?" 하고 모조리 Garbage Collection 해버리게 만드는 주문이니까요. (복구 불가).
여러분의 소중한 데이터를 위해, 무작정 복사(cp)하기 전에 링크(ln)를 고려해보세요.
특히 Node.js를 쓴다면 pnpm은 선택이 아니라 필수입니다. 디스크가 여러분에게 감사할 겁니다.