API 호출 실패는 코드만의 문제가 아니다
서버에 요청을 보냈는데 응답이 오지 않는다. 이때 원인은 코드 한 곳에만 있지 않을 수 있다. 도메인 이름을 IP 주소로 바꾸는 DNS가 실패했을 수도 있고, TCP 연결이 맺어지지 않았을 수도 있다. HTTPS라면 TLS 인증서 검증에서 막혔을 수도 있다. 연결은 성공했지만 서버 애플리케이션이 늦게 응답했을 수도 있다.
네트워크 원리를 알아야 하는 이유는 여기에 있다. 개발자는 단순히 API 호출이 실패했다는 결과만 보는 것이 아니라, 실패가 어느 계층에서 발생했는지 좁혀야 한다. 그래야 재시도를 넣을지, 타임아웃을 늘릴지, DNS 설정을 볼지, 서버 부하를 볼지 판단할 수 있다.
네트워크는 멀리 있는 컴퓨터끼리 데이터를 주고받기 위한 약속의 집합이다. 우리가 코드에서 fetch, axios, HttpClient 같은 API를 호출하면 내부에서는 여러 단계의 약속이 차례로 동작한다. 애플리케이션 코드는 HTTP 요청을 만들고, 운영체제는 TCP 연결을 관리하며, IP는 목적지까지 패킷을 보내고, 물리 네트워크는 전기 신호나 무선 신호로 데이터를 전달한다.
AI 생성 이미지
계층 구조는 외우기보다 문제를 나누기 위한 도구다
네트워크를 설명할 때 자주 등장하는 모델이 OSI 7계층과 TCP/IP 모델이다. OSI 모델은 네트워크 기능을 7단계로 세밀하게 나눈 교육용 기준에 가깝고, TCP/IP 모델은 실제 인터넷 프로토콜 묶음을 설명하는 데 더 직접적이다.
중요한 것은 계층 이름을 암기하는 것이 아니다. 요청이 실패했을 때 어느 책임 영역을 봐야 하는지 나누는 것이 핵심이다.
| 관점 | OSI 모델 | TCP/IP 모델 | 실무에서 보는 현상 |
|---|---|---|---|
| 애플리케이션 | 응용, 표현, 세션 | 애플리케이션 | HTTP, DNS, TLS, JSON, 인증 실패 |
| 전송 | 전송 | 전송 | TCP 연결, 포트, 재전송, UDP 패킷 손실 |
| 인터넷 | 네트워크 | 인터넷 | IP 주소, 라우팅, 패킷 전달 실패 |
| 네트워크 접근 | 데이터 링크, 물리 | 링크 | 이더넷, Wi-Fi, 장비, 케이블, 무선 품질 |
예를 들어 HTTP 500 응답은 보통 애플리케이션 계층 문제다. 서버가 요청을 받았고, 내부 처리 중 오류가 났다는 뜻이다. 반면 connection refused는 목적지 IP까지 도달했지만 해당 포트에서 연결을 받아 주는 프로세스가 없을 때 자주 나타난다. connection timeout은 중간 네트워크, 방화벽, 라우팅 문제, 또는 서버가 응답하지 못하는 상황까지 폭넓게 의심해야 한다.
이처럼 계층 구조는 현실의 장애를 분해하는 지도다. 아래 계층이 실패하면 위 계층은 시작조차 못 한다. TCP 연결이 안 되면 HTTP 요청은 전송되지 않는다. DNS가 실패하면 연결할 IP 주소를 찾지 못한다. HTTPS에서는 TCP 연결 뒤에 TLS 협상까지 성공해야 HTTP 요청을 안전하게 보낼 수 있다.
IP 주소와 포트는 목적지와 입구를 정한다
네트워크 통신에서 IP 주소는 컴퓨터나 네트워크 인터페이스를 찾기 위한 주소다. 집 주소가 건물을 찾게 해 준다면, IP 주소는 패킷이 어느 장비 쪽으로 가야 하는지 알려 준다.
하지만 IP 주소만으로는 충분하지 않다. 한 서버 안에서도 웹 서버, 데이터베이스, 캐시, 관리용 서비스처럼 여러 프로세스가 동시에 네트워크를 사용할 수 있다. 이때 포트가 필요하다. 포트는 한 장비 안에서 특정 네트워크 프로그램을 구분하는 번호다.
예를 들어 example.com:443에 접속한다는 말은 대략 다음 뜻이다.
example.com을 DNS로 조회해 IP 주소를 얻는다.그 IP 주소의 443번 포트로 연결을 시도한다.
443번 포트에서는 보통 HTTPS 서버가 연결을 기다린다.
서버 애플리케이션을 배포할 때 포트가 이미 사용 중이라는 오류를 본 적이 있을 것이다. 일반적으로 같은 프로토콜에서 같은 IP와 같은 포트 조합을 두 프로세스가 동시에 점유할 수 없기 때문이다. 예를 들어 한 프로세스가 0.0.0.0:8080에서 TCP 연결을 받고 있다면, 다른 프로세스가 같은 방식으로 8080 포트를 열려고 할 때 충돌이 날 수 있다.
클라이언트도 연결을 만들 때 임시 포트를 사용한다. 서버는 클라이언트 IP, 클라이언트 포트, 서버 IP, 서버 포트, 그리고 프로토콜 조합으로 연결을 구분한다. 그래서 수많은 클라이언트가 같은 서버의 443번 포트에 동시에 접속해도 각각의 연결을 따로 관리할 수 있다.
라우팅과 NAT는 패킷이 지나가는 길을 만든다
라우팅은 패킷을 목적지까지 보내기 위해 다음으로 어느 장비에 넘길지 결정하는 과정이다. 인터넷은 하나의 거대한 케이블이 아니라 수많은 네트워크가 연결된 구조다. 패킷은 여러 라우터를 거치며 목적지에 가까워진다.
개발자가 라우팅을 직접 구현하는 경우는 드물다. 하지만 장애를 분석할 때는 중요하다. 특정 지역 사용자만 접속이 느리거나, 회사 네트워크에서는 접속되지 않는데 휴대폰 테더링으로는 되는 경우가 있다. 이때는 애플리케이션 코드보다 네트워크 경로, 방화벽, 프록시, DNS 응답 차이를 먼저 의심해야 한다.
NAT는 사설 IP 주소를 공인 IP 주소로 바꾸어 외부 인터넷과 통신하게 하는 기술이다. 집이나 회사 내부의 장비들은 보통 192.168.x.x, 10.x.x.x 같은 사설 IP를 사용한다. 이 주소는 인터넷 전체에서 직접 라우팅되지 않는다. 공유기나 게이트웨이가 내부 장비의 요청을 공인 IP로 바꿔 외부로 보내고, 돌아오는 응답을 다시 내부 장비로 돌려준다.
NAT 덕분에 여러 장비가 하나의 공인 IP를 공유할 수 있다. 하지만 외부에서 내부 장비로 먼저 접속하려면 포트 포워딩, 방화벽 규칙, 로드 밸런서 설정 같은 추가 구성이 필요하다. 로컬 서버는 잘 되는데 외부에서 접속이 안 되는 문제는 애플리케이션보다 NAT나 방화벽 설정에서 발생하는 경우가 많다.
TCP는 신뢰성을 만들기 위해 더 많은 일을 한다
IP는 패킷을 목적지까지 보내려고 노력하지만, 전달을 보장하지 않는다. 패킷은 중간에서 유실될 수 있고, 순서가 바뀔 수 있으며, 중복될 수도 있다. TCP는 이런 불안정한 환경 위에서 신뢰성 있는 바이트 스트림을 제공한다.
TCP 연결은 보통 3-way handshake로 시작한다. 클라이언트가 연결 요청을 보내고, 서버가 받을 준비가 됐다고 응답하며, 클라이언트가 이를 확인하면 연결이 성립한다. 이 과정이 끝나야 양쪽은 서로 데이터를 주고받을 준비가 됐다고 볼 수 있다.
TCP가 신뢰성을 만드는 핵심 장치는 순서 번호와 확인 응답이다. 송신자는 보낸 데이터에 번호를 붙이고, 수신자는 어디까지 받았는지 알려 준다. 일정 시간 안에 확인 응답이 오지 않으면 송신자는 데이터가 유실됐다고 보고 다시 보낸다. 이것이 재전송이다.
이 특성 때문에 패킷 손실은 단순한 실패가 아니라 지연으로 나타나는 경우가 많다. 네트워크 품질이 나쁜 환경에서 API 응답 시간이 들쭉날쭉해지는 이유 중 하나다. TCP는 데이터를 잃지 않기 위해 기다리고 다시 보낸다. 개발자가 보는 타임아웃은 그 기다림이 애플리케이션이 정한 한계를 넘었을 때 발생한다.
TCP에는 흐름 제어도 있다. 수신자가 처리할 수 있는 양보다 송신자가 더 빨리 보내면 버퍼가 넘칠 수 있다. 그래서 수신자는 지금 얼마나 더 받을 수 있는지 윈도우 크기로 알려 준다. 송신자는 이 범위 안에서 데이터를 보낸다. 느린 클라이언트에게 큰 파일을 전송할 때 서버 리소스가 오래 붙잡히는 이유와도 연결된다.
혼잡 제어는 네트워크 전체가 감당할 수 있는 속도를 조절하는 장치다. 네트워크 중간 장비가 붐비면 패킷 손실이나 지연이 늘어난다. TCP는 이를 신호로 보고 전송 속도를 낮추거나, 상황이 좋아지면 조금씩 높인다. 즉 TCP의 속도는 애플리케이션 코드만으로 결정되지 않는다. 네트워크 상태와 상대방 처리 능력의 영향을 함께 받는다.
다만 TCP의 신뢰성에는 비용이 있다. 연결 수립 과정이 필요하고, 손실된 데이터가 있으면 뒤의 데이터까지 애플리케이션에 바로 전달되지 못할 수 있다. 순서를 지키는 것이 장점이지만, 실시간 서비스에서는 오히려 지연을 키울 수 있다.
UDP는 가볍지만 보장하지 않는다
UDP는 TCP보다 단순하다. 연결을 맺는 과정이 없고, 데이터를 독립적인 데이터그램 단위로 보낸다. 데이터그램은 작은 택배 상자처럼 각각 목적지를 가지고 이동한다. UDP는 도착 여부, 순서, 중복 제거를 보장하지 않는다.
이 말은 UDP가 항상 빠르다는 뜻이 아니다. UDP 자체의 오버헤드가 작고 재전송을 강제하지 않을 뿐이다. 네트워크가 막히면 UDP 패킷도 유실되거나 늦게 도착한다. 차이는 손실을 어떻게 다루느냐다. TCP는 신뢰성을 위해 기다리고 다시 보내지만, UDP는 그 판단을 애플리케이션이나 상위 프로토콜에 맡긴다.
그래서 UDP는 실시간성이 중요한 서비스에서 자주 쓰인다. 음성 통화, 화상 회의, 온라인 게임에서는 오래된 데이터가 늦게 도착하는 것보다 일부 손실을 감수하고 최신 상태를 유지하는 편이 낫다. 게임 캐릭터의 1초 전 위치가 뒤늦게 도착해도 큰 의미가 없다. 이런 경우 애플리케이션은 필요한 데이터만 자체적으로 재전송하거나, 손실을 보간하는 방식으로 처리한다.
DNS도 전통적으로 UDP를 많이 사용한다. 짧은 질의와 응답을 빠르게 주고받는 데 적합하기 때문이다. 응답이 크거나 특정 조건에서는 TCP를 사용하기도 하지만, 기본적인 질의와 응답에는 UDP가 널리 쓰인다. 최근에는 UDP 위에서 더 복잡한 신뢰성과 보안 기능을 구현하는 프로토콜도 사용된다. 중요한 점은 UDP가 무책임한 프로토콜이라는 뜻이 아니라, 신뢰성 정책을 전송 계층이 아니라 애플리케이션이나 상위 프로토콜이 설계하도록 남겨 둔다는 점이다.
네트워크 문제를 계층별로 좁히는 습관
실무에서 네트워크 장애를 볼 때는 한 번에 결론을 내리지 않는 편이 좋다. 먼저 이름 해석이 되는지 본다. 도메인이 올바른 IP로 해석되지 않으면 DNS 설정, 캐시, 내부 DNS, 레코드 변경 전파 시간을 확인해야 한다.
다음은 목적지 IP와 포트까지 연결되는지 확인한다. 연결이 거부되면 서버 프로세스가 떠 있는지, 포트를 열었는지, 로드 밸런서가 올바른 대상으로 보내는지 확인한다. 연결이 시간 초과된다면 방화벽, 보안 그룹, 라우팅, NAT, 서버 과부하까지 의심해야 한다.
HTTPS 요청이라면 TLS 단계도 봐야 한다. 인증서 만료, 호스트 이름 불일치, 신뢰할 수 없는 인증기관, 지원하지 않는 TLS 버전 때문에 애플리케이션 요청 전에 실패할 수 있다.
마지막으로 HTTP 응답이 왔다면 네트워크의 많은 단계는 이미 통과한 것이다. 4xx는 대개 요청 형식, 인증, 권한 문제에 가깝고, 5xx는 서버 내부 오류나 의존 시스템 장애를 의심한다. 물론 프록시나 게이트웨이가 만든 502, 503, 504도 있으므로 서버 애플리케이션 로그와 중간 장비 로그를 함께 봐야 한다.
네트워크는 눈에 보이지 않기 때문에 막연하게 느껴진다. 그러나 계층으로 나누면 복잡도가 줄어든다. DNS가 이름을 주소로 바꾸고, IP가 목적지까지 길을 찾고, TCP나 UDP가 전송 방식을 정하고, 애플리케이션 프로토콜이 의미 있는 요청과 응답을 만든다.
개발자에게 필요한 네트워크 지식은 모든 패킷 구조를 외우는 것이 아니다. 실패가 어느 단계에서 발생했는지 구분하고, 각 단계의 비용과 한계를 이해하는 것이다. 이 감각이 있으면 API 타임아웃, 간헐적 연결 실패, 특정 환경에서만 발생하는 접속 문제를 훨씬 빠르게 좁힐 수 있다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.