김우진
인스타 스토리를 올리려고 버튼을 눌렀는데 업로드가 좀처럼 끝나지 않아요. 친구에게 보낸 “어디야?” DM도 전송되지 않고요. 휴대폰에는 분명 5G가 표시되어 있고, 안테나도 충분히 잡혀 있어요.

이럴 때 자연스럽게 “인스타 터졌나?”라는 생각을 하게 돼요. 그런데 인스타그램 서버가 대학 축제 하나 때문에 흔들릴 리는 없어요. 같은 시각, 축제장 밖에 있는 친구의 인스타는 아무 문제 없이 돌아가고 있을 테니까요.
서버는 멀쩡한데 내 화면은 멈춰 있는 거죠. 두 상황은 동시에 성립할 수 있어요. 서버가 요청을 처리하는 능력과, 휴대폰이 서버까지 요청을 보내고 응답을 받는 능력은 서로 다른 문제이기 때문이에요.
올해 축제 웹서비스를 준비하면서 이 구분을 제대로 해둬야 했어요. 인스타그램이라면 서버는 빼고 생각해도 되지만, 저희가 직접 구축하는 서버는 서버의 사정과 네트워크의 사정 둘 다 의심해야 하니까요.
이 차이를 이해하려면, 인터넷을 하나의 덩어리로 보지 않고 계층으로 나누어 볼 필요가 있어요.
컴퓨터 네트워크에는 OSI 7계층이라는 참조 모델이 있어요. 통신에 필요한 역할을 물리 계층부터 응용 계층까지 나누어 설명하는 모델이에요. 각 계층은 아래 계층의 기능을 이용하면서, 위 계층에 필요한 기능을 제공해요.
| 계층 | 담당하는 일 |
|---|---|
| 7. 응용 계층 | HTTP처럼 애플리케이션이 사용하는 통신 규칙을 제공해요. |
| 6. 표현 계층 | 서로 데이터를 해석할 수 있도록 표현 형식 등을 다뤄요. |
| 5. 세션 계층 | 통신하는 양쪽의 대화를 관리하고 동기화해요. |
| 4. 전송 계층 | 양 끝단 사이의 데이터 전달을 담당해요. TCP가 대표적이에요. |
| 3. 네트워크 계층 | IP 주소를 바탕으로 여러 네트워크를 거쳐 패킷을 전달해요. |
| 2. 데이터 링크 계층 | 개별 연결 구간에서 데이터를 전달하고, 전송 매체의 사용을 제어해요. |
| 1. 물리 계층 | 비트를 전파·전기·빛 같은 실제 신호로 전달해요. |
이 구분은 통신의 역할을 이해하기 위한 것이에요. 실제 인터넷이 일곱 개의 독립된 단계를 교과서 그대로 구현하는 것은 아니고, 특히 상위 계층의 기능은 TCP/IP 체계에서 다른 방식으로 묶이기도 해요. TLS 같은 실제 기술을 특정 계층 하나에 억지로 대응시키기보다는, 어떤 역할에서 문제가 생겼는지 구분하는 지도로 보는 편이 정확해요.
그 지도를 들고 축제장으로 돌아가 볼게요.

휴대폰의 상단 어딘가에 있는 안테나 표시는 기본적으로 이동통신 신호의 세기를 나타내요. 통신망에 얼마나 여유가 있는지, 지금 데이터를 얼마나 빠르게 보낼 수 있는지를 직접 보여주는 표시는 아니에요.
여기서 구분해야 할 것이 신호가 도달하는 문제와, 한정된 통신 자원을 나누는 문제예요.
휴대폰마다 기지국까지 전용 무선 통로가 하나씩 생기는 것은 아니에요. 같은 셀을 사용하는 단말들은 한정된 무선 자원을 공유해요. 5G에서는 기지국의 MAC, 즉 데이터 링크 계층에 속하는 기능이 스케줄러를 통해 단말마다 사용할 무선 자원을 배분해요. 이때 전송할 데이터의 양, 무선 상태, 서비스의 품질 요구 등을 고려해요.
따라서 “사람이 많아서 전파끼리 마구 충돌한다”라고만 설명하면 중요한 부분을 놓치게 돼요. 이동통신망은 원래 여러 단말의 전송을 조정하도록 설계되어 있어요. 문제는 조정을 잘하더라도 배분할 수 있는 자원 자체가 무한하지 않다는 점이에요. 대규모 행사에서 무선 접속망의 수요와 수용 능력을 따로 분석하고 예측하는 이유도 여기에 있어요.

좀 더 기술적으로 엄밀히 따지면, 무선자원 할당이 아예 안되는건 아니고 자원을 계속하여 아주 짧은 시간동안 받았다 줬다 하는데 한번 받고 줬을 때 다음 받을 때까지의 텀이 길어서(다른 사람들도 써야하니깐) 연결이 안되는 것처럼 느끼는거에요
축제를 이 관점에서 보면 특성이 더 선명해져요.
학생들이 캠퍼스 곳곳에 흩어져 인터넷을 사용하는 상황과, 같은 시각에 무대 앞에 모여 사용하는 상황은 달라요. 여기에 가수가 등장하는 순간을 생각해보세요. 무대 앞의 사람들이 거의 동시에 휴대폰을 들어요. 스토리를 올리고, 릴스를 찍고, 친구에게 DM을 보내요.
즉 축제는 단순히 이용자가 많은 환경이 아니라, 이용자의 공간적 밀도와 행동의 동시성이 함께 높아지는 환경이라고 볼 수 있어요.
안테나 네 칸과 무한 로딩은 서로 모순되지 않아요. 신호는 좋은데, 데이터를 주고받을 자원이 충분하지 않을 수 있으니까요.
무선 구간을 통과했다고 통신이 끝나는 것은 아니에요. 데이터는 IP 패킷의 형태로 여러 네트워크를 거쳐 목적지로 이동해요. 여기서 IP는 패킷이 반드시 도착하거나 순서대로 도착하는 것까지 보장하지는 않아요.
이동 과정에서 어떤 장비로 들어오는 데이터가 내보낼 수 있는 양보다 많아지면, 데이터는 큐에서 기다리게 돼요. 이런 대기는 무선 접속 구간뿐 아니라 중간 네트워크 장비에서도 발생할 수 있어요. 큐가 길어지면 지연 시간이 늘어나고, 버퍼가 감당하지 못하거나 혼잡 제어 정책에 따라 패킷이 버려질 수도 있어요. 그래서 축제장의 느린 인터넷을 무조건 특정 기지국 하나의 문제로 단정할 수는 없어요.
이때 구분해야 하는 두 지표가 있어요.
처리량(throughput) 은 일정 시간에 실제로 얼마나 많은 데이터를 전달했는지를 뜻해요. 반면 지연 시간(latency) 은 데이터를 전달하는 데 걸리는 시간이에요. 특히 요청을 보내고 답을 받기까지의 왕복 시간을 RTT라고 해요. 릴스 영상 하나를 올리는 속도와, 좋아요 하나가 반영되는 속도는 같은 지표가 아니에요.
그래서 “DM 한 줄인데 왜 안 보내지지?”라는 상황도 가능해요. 데이터의 양은 적어도, 전송 기회를 기다리고 네트워크를 왕복하는 시간은 필요하니까요.
여기에 4계층의 TCP가 관여해요. TCP는 손실된 데이터를 재전송하고 순서를 맞추어, 애플리케이션에 신뢰할 수 있는 바이트 스트림을 제공해요. 하지만 이것이 정해진 시간 안에 반드시 전달한다는 뜻은 아니에요. 손실을 복구하는 동안에는 추가 시간이 필요하고, 연결 자체가 실패할 수도 있어요.
TCP는 혼잡에도 대응해요. 대표적인 혼잡 제어 방식은 네트워크에 한꺼번에 내보낼 수 있는 데이터의 양을 congestion window 로 제한하고, 손실 등 혼잡의 신호가 나타나면 전송량을 줄여요. 이미 혼잡한 네트워크에 계속 같은 속도로 데이터를 밀어 넣지 않도록 하는 것이죠.
그러니까 인터넷이 느려졌다는 현상에는, 통신이 고장 난 경우만 있는 것이 아니에요. 사람이 몰려 망이 혼잡해졌으며, 그 혼잡을 더 악화시키지 않기 위한 제어가 작동하는 경우도 있어요.
여기서부터는 인스타그램 대신, 저희가 직접 설계/개발 중인 축제 웹을 예로 들게요. 브라우저가 축제 웹에 접속하는 과정부터요.
TCP 기반의 HTTPS 연결을 새로 만드는 경우에는 서버 주소를 알아내는 DNS 조회가 필요할 수 있고, TCP 연결을 맺은 뒤 TLS로 안전한 통신을 위한 협상을 진행해요. 이후 HTTP로 필요한 내용을 요청해요. DNS 결과나 연결을 재사용하는지, 어떤 프로토콜을 사용하는지에 따라 과정과 왕복 횟수는 달라져요. 여기서는 익숙한 TCP 기반 접속을 예로 들고 있는 것이에요.
중요한 것은 모든 절차가 한 번에 끝나지는 않는다는 점이에요. 먼저 받은 결과가 있어야 다음 요청을 보낼 수 있는 경우가 있어요.
예를 들어 어떤 화면이 다음과 같이 만들어져 있다고 가정해볼게요.
“기본 정보를 받은 다음, 그 결과로 상세 정보를 요청하고, 다시 그 결과로 대기 상태를 요청한다.”
서로 의존하는 요청이 세 번이에요. 이미 연결이 만들어져 있고, 각 요청에 한 번의 왕복이 필요하며, 서버 처리와 데이터 전송 시간은 작다고 단순화해볼게요. RTT가 50밀리초라면 왕복에 약 150밀리초가 필요하지만, RTT가 500밀리초라면 약 1.5초가 필요해요.
응답 데이터의 크기는 그대로예요. 서버 코드도 그대로고요. 달라진 것은 네트워크의 왕복 시간인데, 순차적인 요청 구조가 그 비용을 반복해서 지불하게 만드는 거예요.
이런 요청의 의존 관계는 브라우저에서도 나타나요. HTML을 받아 해석해야 필요한 자원을 발견할 수 있고, 자바스크립트를 실행한 뒤에야 추가 데이터를 요청하는 구조도 가능해요. 그래서 웹 성능은 파일 크기뿐 아니라, 필요한 정보를 얻기까지 통신이 어떤 순서로 이어지는지에도 영향을 받아요.
축제 웹에 적용하면 최적화의 질문도 달라져야 해요.
이미지 용량을 줄이는 것은 보내야 할 데이터의 양을 줄이는 일이에요. 불필요한 순차 요청을 없애는 것은 기다려야 할 왕복의 횟수를 줄이는 일이고요.
개발자의 코드에서는 짧은 API 호출 몇 줄이어도, 축제장에서는 각각이 배로 길어지는 기다림이 될 수 있어요.
여기까지는 주로 성능의 이야기였어요. 그런데 "이 정보 추가해줘" 같은 데이터를 변경하는 기능에서는 문제가 한 단계 더 복잡해져요.
등록 버튼을 눌렀는데 서버로부터 응답이 너무 오랫동안 오지 않아, 타임아웃이 발생했다고 해볼게요. 이때 타임아웃은 “너가 말했던 정보가 추가되지 않았다”는 뜻일까요?
그렇게 단정할 수는 없어요. 요청이 서버에 도착하지 않았을 수도 있지만, 서버가 등록을 완료한 뒤 응답만 돌아오지 못했을 수도 있어요. 유저 입장에서 응답을 받지 못했다는 사실만으로는 서버에서 무슨 일이 일어났는지 확정할 수 없어요.

여기서 놓치기 쉬운 지점이 있어요.
“TCP가 신뢰성을 보장한다면 중복이나 유실도 알아서 해결해주는 것 아닌가?”
TCP가 다루는 것은 연결 안에서의 바이트 전달이에요. 사용자가 버튼을 다시 눌러 HTTP 요청을 한 번 더 보내면, 그것은 애플리케이션이 새롭게 보낸 데이터예요. TCP는 두 요청이 “같은 대기 신청을 다시 시도한 것”인지 판단하지 않아요. 두 HTTP 요청을 모두 정상적으로 전달하는 것과, 대기 등록을 한 번만 만드는 것은 별개의 일이에요.
그래서 애플리케이션에는 멱등성(idempotency) 이 필요해요. 같은 신청의 재시도라는 것을 식별할 수 있도록 키를 부여하고, 같은 키로 다시 요청해도 대기 등록이 추가로 생기지 않도록 처리하는 방식이에요. 이때 식별 키를 기록하는 일과 실제 등록을 만드는 일도 함께 안전하게 처리되어야 해요. 키만 저장되거나 등록만 만들어지는 틈이 생기면 안 되니까요.
이 부분이 계층을 나누어 보는 이유를 잘 보여줘요.
하위 계층의 신뢰성이, 상위 계층에서 원하는 업무 결과까지 자동으로 보장하지는 않아요.
한 가지 흥미로운 역설이 더 있어요.
네트워크가 혼잡해지면 전송 계층은 전송량을 조절해요. 그런데 응용 프로그램은 "어? 내가 보낸거 왜 처리가 안되지? 제대로 안갔나?"라고 판단할 수 있죠. 즉, 응답이 늦어진다는 이유로 같은 요청을 계속 다시 보낼 수 있어요. 재시도는 일시적인 장애를 극복하는 데 유용하지만, 과부하 상황에서는 일을 더 늘려 복구를 방해할 수도 있어요.
각각의 판단만 보면 이해할 수 있어요. 한쪽은 혼잡을 줄이려 하고, 다른 쪽은 사용자의 요청을 성공시키려 해요. 하지만 둘을 합치면, 느려진 시스템에 더 많은 요청이 몰리는 결과가 나올 수 있는 거예요.
이 때문에 재시도에는 횟수 제한과 간격이 필요해요. 반복할수록 간격을 늘리는 지수 백오프를 사용하고, 여러 클라이언트가 같은 순간에 다시 요청하지 않도록 간격에 무작위성을 더하는 jitter를 적용하기도 해요. 모두가 동시에 실패한 뒤 정확히 같은 시간만 기다리면, 다음 시도에서도 다시 한꺼번에 몰릴 수 있기 때문이에요. (idea by @안시현 )
그런데 소프트웨어뿐만 아니라 인간의 행동도 비슷한 방향으로 움직여요. 스토리 업로드가 멈추면 앱을 껐다 켜고, 피드를 몇 번이고 당겨서 새로고침하죠. 축제 웹의 등록 버튼 앞에서도 마찬가지고요.
그래서 대기 등록의 결과를 명확하게 안내하고, 재시도를 안전하게 만드는 일은 단순히 친절한 화면을 만드는 일이 아니에요. 불필요한 요청이 더 생기는 상황을 줄이기 위한 설계이기도 해요.
이제 처음의 질문으로 돌아올 수 있어요.
인스타그램은 서버를 빼고 생각해도 되는 예였어요. 저희가 만드는 축제 서비스는 그럴 수 없어요.
서버가 충분히 많은 요청을 처리할 수 있더라도, 휴대폰과 기지국 사이의 무선 자원이 부족하다면 서버를 늘리는 것만으로 그 자원이 생기지는 않아요. 반대로 무선 구간이 원활해도 서버나 데이터베이스가 병목이면 서비스는 느려질 수 있어요.
따라서 저희 축제 같이 밀집된 지역의 부하를 처리해야하는 웹서버를 개발하는 팀은 서로 다른 두 질문에 답해야 한다고 생각해요.
1. 많은 요청이 도착했을 때, 서버가 감당할 수 있는가?
2. 요청과 응답이 늦어지거나 끊길 때도, 사용자가 하려던 일을 안전하게 마칠 수 있는가?
첫 번째를 확인하려면 부하를 만들어 서버의 처리 능력을 살펴봐야 해요. 두 번째를 확인하려면 단말 쪽에 지연과 연결 단절을 만들어놓고, 등록 결과가 불명확해지지는 않는지, 재시도로 데이터가 중복되지는 않는지, 연결이 돌아온 뒤 상태를 다시 확인할 수 있는지 봐야 해요. 저희에게 필요한 테스트는 이 두 관점을 함께 포함해야 해요.
물론, 근본적인 원인은 기지국으로부터 받는 해당 지역의 무선 자원이 부족하단 것이지만 이것은 7계층 애플리케이션 개발자가 해결할 수 없는 영역이라서 우리는 우리의 최선을 다해야해요.
(이 문제를 풀고자한다면 임시적인 이동식 기지국을 증설하여 해결하는 것이 가장 현실적이에요)
관측도 마찬가지예요. 서버 내부의 처리 시간과 오류율만 보는 것과, 사용자가 실제로 서비스를 이용할 수 있는지 확인하는 것은 달라요.
가령 서버에 도착한 요청의 처리 시간이 모두 짧더라도, 어떤 학생의 요청은 아직 서버에 도착하지 못했을 수 있어요. 그 학생의 기다림은 서버 내부의 처리 시간에는 포함되지 않아요.
‘서버 정상’은 중요한 관측 결과지만, ‘학생이 정상적으로 사용하고 있다’는 결론과 같지는 않은 거예요.
축제 때 인터넷이 느린 이유를 따라가다 보면, 결국 전파에서 시작한 이야기가 애플리케이션의 책임으로 이어져요. 한정된 무선 자원, 네트워크의 대기와 손실, 전송 계층의 복구, 상위 계층의 왕복과 재시도가 하나의 사용자 경험 안에서 만나는 것이죠.
저희가 기지국의 자원 배분이나 통신사망을 직접 바꿀 수는 없어요. 하지만 그 환경에서 불필요하게 여러 번 왕복하는 화면을 만들지, 응답을 못 받았다는 이유로 같은 신청을 두 번 처리할지는 애플리케이션의 설계에 달려 있어요.