목차
프로그램에서 파일을 열면 정수 하나가 돌아온다. 소켓을 열어도, DB에 연결해도 마찬가지다. 이 작은 번호 하나로 온갖 자원을 가리키는 방식이 파일 디스크립터다. 연결 풀이 아끼던 그 “연결”의 밑바닥이기도 하다.
파일도 소켓도 연결도 같은 번호로 가리킨다
파일을 열면 3 같은 정수가 돌아오고, 이후 읽기·쓰기는 그 3으로 한다. 놀라운 건 네트워크 소켓도, 파이프도, 심지어 다른 프로그램과의 통신 통로도 똑같이 정수 하나로 가리킨다는 점이다.
파일 열기 → 3
소켓 열기 → 4
DB 연결 열기 → 5 (실은 그 밑이 소켓이다)종류가 제각각인데 프로그램은 다 같은 방식으로 다룬다 - 번호로 가리키고, 그 번호로 읽고 쓰고, 다 쓰면 그 번호를 닫는다. 이 번호가 파일 디스크립터(file descriptor, fd) 다.
모든 것은 파일이다
이게 가능한 건 유닉스 계열 OS의 오래된 발상 때문이다 - “모든 것을 파일처럼 다룬다.” 진짜 파일이든 네트워크 소켓이든, “열고 · 읽고 · 쓰고 · 닫는다”는 같은 동작으로 취급한다.
덕분에 프로그램은 자원의 종류마다 다른 방식을 배울 필요가 없다. 파일에 쓰듯 소켓에 쓰고, 파일을 읽듯 네트워크를 읽는다. 이 통일 덕에 fd라는 번호 하나가 온갖 자원의 공통 손잡이가 된다.
이건 유닉스·리눅스·맥 계열의 방식이다. 윈도우는 비슷한 걸 “핸들(handle)“이라 부르며 세부가 다르다. 개념 - “자원을 번호/손잡이로 가리킨다” - 은 같다.
fd 테이블은 프로세스마다 있다
그 번호는 어디에 적혀 있나. 프로세스가 받는 세 가지 중 “자원 목록”이 바로 이것이다. 프로세스마다 자기 fd 테이블이 있고, 번호는 그 테이블의 칸을 가리킨다.
프로세스 A의 fd 테이블 프로세스 B의 fd 테이블
3 → 로그 파일 3 → 소켓
4 → 소켓 4 → 설정 파일그래서 A의 3과 B의 3은 전혀 다른 것을 가리킨다 - 가상 메모리에서 같은 주소가 다른 물리 칸이었던 것과 똑 닮았다. fd는 그 프로세스 안에서만 뜻이 있는 번호다. 프로세스가 죽으면 이 테이블이 통째로 정리되며 열어둔 것들이 다 닫힌다.
소켓도 fd라서 연결도 fd다
네트워크 연결은 소켓이고, 소켓은 fd다. 그러니 connection-pooling 글의 “DB 연결”도 밑을 보면 fd 하나다. DB 연결을 하나 열면, 그 밑에서 소켓 fd 하나가 열려 DB 서버와의 통신 통로를 잡는다.
이 사실이 왜 중요하냐면, “연결”이 추상적인 개념이 아니라 OS가 세는 실물 자원이라는 뜻이기 때문이다. 연결을 아끼라는 말은 곧 fd를 아끼라는 말이다.
fd는 유한하다
fd 테이블은 무한하지 않다. 한 프로세스가 열 수 있는 fd 수에는 상한이 있다(OS가 정하고 조절 가능). 파일·소켓·연결을 계속 열기만 하고 안 닫으면 이 상한에 부딪힌다.
그 순간 나오는 게 그 유명한 “too many open files” 오류다. 새 파일을 못 열고, 새 연결을 못 맺고, 심지어 새 요청을 못 받는다. 자원이 없어서가 아니라 번호를 다 써버려서다.
안 닫으면 샌다
fd도 힙 메모리처럼 스스로 안 사라진다. 열었으면 닫아야 그 번호가 테이블에서 풀려 다시 쓰인다. 안 닫으면 fd가 야금야금 쌓이는 fd 누수가 된다 - 한동안 멀쩡하다 어느 순간 상한에 닿아 “too many open files”로 터진다.
connection-pooling 글이 “빌렸으면 반납해야 한다”고 그토록 강조한 이유가 이것이다. 연결을 안 반납하면 그 밑 fd가 안 닫히고, 풀이 마르는 동시에 fd도 샌다. 반납은 예의가 아니라 유한한 번호를 되돌려주는 일이다.
실무에서: 번호가 새는 걸 잡는다
- “too many open files”는 fd 누수 신호 - 어딘가에서 파일·소켓·연결을 열고 안 닫는다. 잘 도는 것 같다가 시간이 지나 터지면 이걸 의심한다.
- 열린 fd 수를 지표로 본다 - 프로세스가 연 fd 수가 시간에 따라 계속 우상향하면 누수다. 평평하게 유지되면 건강하다.
- 닫기를 코드가 보장하게 - “쓰고 나면 반드시 닫는” 구조(언어의 자동 닫기 구문·풀의 반납 보장)를 써서, 사람이 잊어도 닫히게 한다. 연결 풀이 반납을 관리해 주는 것도 이 때문이다.
- 상한을 필요만큼 올린다 - 많은 연결을 다뤄야 하는 서버는 fd 상한 자체를 올려야 할 때가 있다. 다만 누수를 상한 올리기로 덮으면 안 된다 - 그건 터지는 시점만 미룬다.
정리
- 파일·소켓·연결을 프로그램은 전부 작은 정수(fd) 하나로 가리킨다 - “모든 것은 파일” 발상 덕이다.
- fd 테이블은 프로세스마다 따로라, 같은 번호도 프로세스가 다르면 다른 것을 가리킨다.
- 연결은 밑을 보면 소켓 fd - 연결을 아끼라는 건 유한한 fd를 아끼라는 말이다.
- fd는 유한하고 안 닫으면 샌다 - 그래서 “too many open files”가 나고, 반납이 그토록 중요하다.
다음 글은 시리즈를 닫으며, 그 fd를 통한 I/O가 스레드를 어떻게 멈춰 세우는지 - 블로킹 I/O로 밑바닥을 백엔드 개념에 잇는다.