경과0ms/ ~186ms

POST /api/orders

요청 하나의 186ms

서울의 한 사용자가 지하철에서 폰으로 쇼핑 앱을 열고 주문하기를 눌러요. 화면에 "주문 완료"가 뜨기까지 눈 깜짝할 사이지만, 그동안 요청 하나는 전파와 빛이 되어 도시를 가로지르고 여러 대의 컴퓨터를 거쳐 돌아와요.

아래로 스크롤하면서 그 여정을 한 단계씩 따라가 봐요.
단계마다 무슨 일이 있는지 논리적, 물리적 상황을 함께 보여드려요.

DNSDomain Name System사람이 읽는 도메인 이름을 컴퓨터가 쓰는 IP 주소로 바꿔 주는 전화번호부 같은 시스템이에요., TCPTransmission Control Protocol데이터가 빠짐없이, 보낸 순서대로 도착하도록 책임지는 전송 규칙이에요., TLSTransport Layer Security주고받는 내용을 암호화하고 상대 서버가 진짜인지 확인하는 보안 규칙이에요. https의 s가 이것이에요.처럼 파란 칩으로 표시된 용어는 마우스를 올리거나 탭하면 한글 설명이 떠요.

Step 0

버튼을 누르는 순간

손가락이 화면에 닿고, 브라우저가 요청을 만들기 시작해요.

+5ms

논리
touch →click →fetch() →
물리
  1. 터치 센서
  2. 터치 컨트롤러
  3. AP(프로세서)

요청이 가는 중: touch

손가락이 "주문하기" 버튼에 닿는 순간부터 여정이 시작돼요. 화면 아래의 터치 센서가 손가락 위치를 알아채고, 그 좌표가 폰의 프로세서로 전달돼요.

브라우저는 그 좌표에 어떤 요소가 있는지 찾아 버튼의 click 이벤트를 일으키고, 우리가 붙여 둔 onClick 함수가 실행돼요. 그 안에서 fetch()브라우저와 Node.js에서 HTTP 요청을 보내는 표준 함수예요. 결과는 Promise로 돌려줘요.가 호출되면서 비로소 HTTP 요청이 만들어지기 시작해요.

+ 더 알아보기

요즘 폰 화면은 대부분 정전식 터치 센서예요. 유리 아래에 가는 전극이 격자로 깔려 있고, 손가락이 닿으면 그 지점의 전기적 성질이 살짝 달라져요. 터치 컨트롤러 칩이 격자를 계속 훑다가 변화가 생긴 위치를 계산해 프로세서(AP)에 알려요.

프로세서에서는 OS가 이 입력을 받아 지금 화면 앞에 있는 앱, 여기서는 브라우저에 넘겨요. 브라우저는 화면 좌표와 페이지 요소들의 위치를 맞춰 보는 hit test화면의 어느 좌표를 눌렀을 때 그 자리에 있는 요소가 무엇인지 찾아내는 계산이에요.로 눌린 버튼을 찾아요.

Step 1

주소를 읽고 캐시부터 확인하기

보내기 전에, 이미 가진 답이 있는지 먼저 살펴봐요.

+1ms

논리
URL parse →cache lookup →
물리
  1. AP(프로세서)
  2. 메모리(RAM)

요청이 가는 중: URL parse

fetch에 넘긴 주소 https://shop.example.com/api/orders를 브라우저가 쪼개 읽어요. 어떤 규칙으로(https), 어느 서버에(shop.example.com), 어떤 경로로(/api/orders) 보낼지 정리하는 거예요.

GET 요청이라면 여기서 HTTP cache전에 받은 응답을 저장해 두었다가 같은 요청에 다시 쓰는 브라우저의 저장소예요.에 쓸 만한 답이 있는지 먼저 봐요. 하지만 주문은 서버의 상태를 바꾸는 POST 요청이라, 브라우저는 저장해 둔 답을 쓰지 않고 네트워크로 보내요.

+ 더 알아보기

이 단계는 전부 폰 안에서 일어나요. 프로세서가 주소 문자열을 해석하고, 캐시와 이미 열려 있는 연결 목록을 메모리(RAM)에서 찾아봐요. 그래서 시간이 아주 조금 걸려요.

페이지와 API의 도메인이 다르면 브라우저는 본 요청 전에 "이런 요청을 보내도 되나요?"라고 묻는 preflight다른 도메인으로 특별한 요청을 보내기 전에 브라우저가 OPTIONS 요청으로 먼저 허락을 묻는 절차예요. 요청을 하나 더 보내요. JSON을 POST하는 요청이 대표적이에요. 이 시나리오는 페이지와 API가 같은 도메인이라 그 왕복이 없어요.

Step 2

이름으로 주소 찾기

도메인 이름을 서버의 IP 주소로 바꿔요.

+36ms

논리
DNS query →← DNS response
물리
  1. 폰 modem
  2. 기지국
  3. 통신사 DNS 서버
DNS query
DNS response

요청이 가는 중: DNS query

shop.example.com이라는 이름만으로는 패킷을 보낼 수 없어요. 인터넷은 IP 주소Internet Protocol인터넷에 연결된 기기마다 붙는 주소 체계예요. 패킷은 이 주소를 보고 길을 찾아가요.로 길을 찾기 때문에, 이름을 주소로 바꾸는 DNSDomain Name System사람이 읽는 도메인 이름을 컴퓨터가 쓰는 IP 주소로 바꿔 주는 전화번호부 같은 시스템이에요. 조회가 먼저 필요해요.

브라우저와 OS에 기억해 둔 답이 없으면 폰은 통신사의 resolver이름을 대신 찾아 주는 DNS 서버예요. 모바일에서는 보통 통신사가 운영하는 서버를 써요.에게 물어봐요. 질문과 답은 보통 UDPUser Datagram Protocol연결 인사 없이 패킷을 바로 보내는 가벼운 전송 규칙이에요. 도착 확인은 하지 않아요. 패킷 하나씩이라 왕복 한 번이면 끝나요.

+ 더 알아보기

통신사 resolver도 답을 모르면 대신 찾아 나서요. 루트 서버에 .com을 맡은 서버가 어디인지 묻고, 그 서버에 example.com을 맡은 서버를 묻고, 마지막으로 그 도메인을 관리하는 서버(예: Route 53)에게서 실제 주소를 받아요.

받은 답에는 TTLTime To LiveDNS 답을 몇 초 동안 기억해 둬도 되는지 알려 주는 값이에요.이 붙어 있어서 그 시간 동안은 resolver, OS, 브라우저가 답을 기억해 둬요. 그래서 실제로는 이 단계가 몇 ms 만에 끝나거나 아예 생략되는 경우가 많아요.

물리적으로는 질문 패킷이 폰 modem에서 전파가 되어 기지국까지 가고, 통신사 망 안의 DNS 서버까지만 다녀와요. 아직 AWS까지는 가지 않아요.

Step 3

서버와 연결 맺기

말을 걸어도 되는지 서로 확인해요.

+36ms

논리
SYN →← SYN-ACKACK →
물리
  1. 폰 modem
  2. 기지국
  3. 통신사 코어망
  4. AWS 서울
SYN
SYN-ACK
ACK

요청이 가는 중: SYN

서버의 IP 주소를 알았으니 이제 말을 걸 차례예요. 그런데 주문 내용을 바로 보내지는 않아요.

먼저 TCPTransmission Control Protocol데이터가 빠짐없이, 보낸 순서대로 도착하도록 책임지는 전송 규칙이에요. 연결을 열어서 서로 주고받을 준비가 됐는지부터 확인해요. 폰이 SYNSynchronize"연결하고 싶어요"라는 뜻으로 처음 보내는 TCP 신호예요.을 보내고, 서버가 SYN-ACKSynchronize-Acknowledge"좋아요, 나도 준비됐어요"라고 서버가 돌려주는 TCP 신호예요.로 답하고, 폰이 ACKAcknowledgment"잘 받았어요"라는 확인 신호예요.로 받았다고 알리면 연결이 열려요. 이 세 번의 인사를 3-way handshakeTCP 연결을 열기 전에 SYN, SYN-ACK, ACK 세 신호로 양쪽이 준비됐는지 확인하는 절차예요.라고 불러요.

데이터는 한 바이트도 보내지 않았는데 신호가 한 번 왕복하는 시간, 즉 RTTRound-Trip Time신호가 상대에게 갔다가 답이 돌아오기까지 걸리는 왕복 시간이에요. 하나가 인사에 쓰였어요.

+ 더 알아보기

물리적으로 따라가 보면, SYNSynchronize"연결하고 싶어요"라는 뜻으로 처음 보내는 TCP 신호예요. 하나는 폰 modem에서 전파가 되어 가까운 기지국으로 날아가요. 기지국부터는 유선이에요. 통신사 코어망을 지나 광케이블로 된 인터넷 백본을 타고 AWS 서울 리전의 데이터센터로 들어가요.

같은 도시 안이라도 무선 구간에서 순서를 기다리는 시간과 여러 장비를 거치는 시간이 쌓여요. 그래서 모바일에서는 이 왕복 한 번에 수십 ms가 걸리는 일이 흔해요.

목적지는 443번 port한 컴퓨터 안에서 어느 프로그램에게 온 데이터인지 가르는 번호예요. https는 보통 443번을 써요.예요. 그 port에서는 ALBApplication Load BalancerAWS의 load balancer예요. 들어온 요청을 뒤에 있는 여러 서버에 나눠 줘요.가 연결을 기다리고 있다가 SYN-ACKSynchronize-Acknowledge"좋아요, 나도 준비됐어요"라고 서버가 돌려주는 TCP 신호예요.를 돌려줘요.

HTTP/3Hypertext Transfer Protocol version 3TCP 대신 QUIC 위에서 동작하는 최신 HTTP 버전이에요.는 TCPTransmission Control Protocol데이터가 빠짐없이, 보낸 순서대로 도착하도록 책임지는 전송 규칙이에요. 대신 QUICUDP 위에서 연결 수립과 암호화를 한 번에 처리하는 전송 규칙이에요. HTTP/3가 이걸 써요.을 써요. TCP 인사와 다음 단계의 TLSTransport Layer Security주고받는 내용을 암호화하고 상대 서버가 진짜인지 확인하는 보안 규칙이에요. https의 s가 이것이에요. 인사를 따로 하면 왕복이 두 번 필요한데, QUIC은 둘을 묶어 한 번에 끝내요.

Step 4

둘만 아는 암호 정하기

서버가 진짜인지 확인하고, 대화를 암호화할 키를 만들어요.

+48ms

논리
ClientHello →← ServerHelloFinished →
물리
  1. 폰 modem
  2. 기지국
  3. 통신사 코어망
  4. AWS 서울
ClientHello
ServerHello
Finished

요청이 가는 중: ClientHello

연결은 열렸지만, 이 위로 오가는 내용은 아직 길목에 있는 누구나 볼 수 있어요. 그래서 주문 정보를 보내기 전에 TLSTransport Layer Security주고받는 내용을 암호화하고 상대 서버가 진짜인지 확인하는 보안 규칙이에요. https의 s가 이것이에요. 인사를 한 번 더 해요.

폰이 ClientHelloTLS 인사의 첫 메시지예요. 클라이언트가 쓸 수 있는 암호 방식과 키 재료를 보내요.로 쓸 수 있는 암호 방식과 키 재료를 보내면, 서버는 ServerHello서버가 고른 암호 방식과 자신의 키 재료를 담아 돌려주는 TLS 메시지예요.로 답해요. 이 순간 양쪽이 같은 키를 계산해 내서, 뒤이어 오는 서버의 인증서이 공개 키의 주인이 어떤 도메인인지, 믿을 수 있는 기관이 서명해 증명한 파일이에요.부터는 이미 암호화된 채로 와요. 폰이 인증서를 확인하고 Finished를 보내면 인사가 끝나고, 주문 내용도 암호화되어 오가요. TLS 1.3Transport Layer Security주고받는 내용을 암호화하고 상대 서버가 진짜인지 확인하는 보안 규칙이에요. https의 s가 이것이에요.에서는 이 과정이 왕복 한 번이에요.

+ 더 알아보기

신기한 점은 암호화에 쓸 키가 네트워크로 한 번도 오가지 않는다는 거예요. 양쪽이 각자 만든 값의 일부만 교환하고, 같은 계산을 해서 같은 비밀 키에 도착해요. 이 방식을 ECDHEElliptic Curve Diffie-Hellman Ephemeral양쪽이 일부 값만 교환하고도 같은 비밀 키를 계산해 내는 키 교환 방식이에요.라고 해요.

인증서는 "이 공개 키의 주인은 shop.example.com이 맞다"는 증명서예요. 폰은 인증서에 적힌 도메인이 접속한 주소와 같은지, 기간이 지나지 않았는지, 폰이 믿는 CACertificate Authority인증서에 서명해 주는 기관이에요. 기기와 브라우저는 믿을 수 있는 CA 목록을 미리 가지고 있어요.까지 서명이 이어지는지 확인해요.

이 시나리오에서는 ALBApplication Load BalancerAWS의 load balancer예요. 들어온 요청을 뒤에 있는 여러 서버에 나눠 줘요.가 인증서를 들고 있다가 암호화를 풀어요. ALB 뒤의 Node 서버는 같은 네트워크 안에서 평문 HTTP로 요청을 받아요.

Step 5

서버 건물에 도착

Load balancer가 요청을 받아 Node 서버 한 대에 넘겨요.

+20ms

논리
POST /api/orders →forward →
물리
  1. ALB
  2. 스위치
  3. 서버 NIC
  4. CPU

요청이 가는 중: POST /api/orders

드디어 주문 내용이 담긴 요청 POST /api/orders가 출발해요. HTTP/2Hypertext Transfer Protocol version 2연결 하나로 여러 요청을 동시에 주고받을 수 있게 개선된 HTTP 버전이에요. 형식으로 암호화된 채 같은 길을 지나 AWS 서울 리전의 ALBApplication Load BalancerAWS의 load balancer예요. 들어온 요청을 뒤에 있는 여러 서버에 나눠 줘요.에 도착해요.

ALB는 암호화를 풀고, 뒤에 있는 Node 서버 여러 대 중 health checkLoad balancer가 서버에 주기적으로 요청을 보내 살아 있는지 확인하는 것이에요.를 통과한 한 대를 골라 요청을 넘겨요. 이 구간은 같은 데이터센터 안이라 아주 가까워요.

+ 더 알아보기

ALB와 Node 서버 사이는 기본적으로 HTTP/1.1이에요. ALB는 서버와 맺은 연결을 열어 두고 여러 요청에 돌려 써요.

요청 패킷이 데이터센터 스위치를 거쳐 서버의 NICNetwork Interface Card서버를 네트워크에 연결하는 하드웨어예요. 들어온 신호를 데이터로 바꿔 메모리에 넣어요.에 도착하면, NIC는 데이터를 메모리에 직접 써 넣고 interrupt하드웨어가 "처리할 게 생겼어요"라고 CPU를 불러 세우는 신호예요.로 CPU를 불러요. CPU는 하던 일을 잠깐 멈추고 kernelOS의 핵심 부분이에요. 하드웨어를 직접 다루고 네트워크 데이터를 프로그램에 전해 줘요.의 네트워크 코드를 실행해서, 데이터를 해당 연결의 수신 버퍼에 쌓아 둬요.

여기까지 Node.js는 아무것도 하지 않았어요. 데이터는 kernel 안에서 누군가 가져가기를 기다리고 있어요.

Step 6

Node.js가 일하는 시간

event loop가 요청을 집어 들고 여러분의 코드를 실행해요.

+2ms

논리
poll →handler() →
물리
  1. CPU core
  2. 메모리(RAM)

요청이 가는 중: poll

kernel에 쌓인 요청을 Node.js가 가져갈 차례예요. Node.js의 event loopNode.js가 할 일 목록을 계속 돌면서 준비된 작업을 하나씩 실행하는 반복 구조예요.는 libuvNode.js 밑에서 네트워크·파일 같은 입출력을 OS와 대신 주고받는 C 라이브러리예요.를 통해 "읽을 데이터가 생긴 연결이 있으면 깨워 줘"라고 OS에 맡겨 두고 기다려요.

데이터가 도착하면 event loop가 poll 단계에서 깨어나 HTTP 요청을 해석하고, 우리가 작성한 핸들러(예: NestJS 컨트롤러)를 main threadNode.js가 JavaScript 코드를 실행하는 단 하나의 실행 흐름이에요.에서 실행해요.

+ 더 알아보기

리눅스 서버에서 libuv는 epoll리눅스에서 많은 연결 중 데이터가 도착한 것만 골라 알려 주는 OS 기능이에요.이라는 OS 기능으로 수많은 연결을 한꺼번에 지켜봐요. 연결마다 thread를 따로 두지 않아도 되는 이유예요.

핸들러 안에서 await로 DB를 기다리는 동안 main thread는 쉬지 않아요. 그 사이 다른 사용자의 요청을 처리하다가, DB 답이 오면 멈춰 두었던 핸들러를 이어서 실행해요.

물리적으로는 CPU core 하나가 이 main thread를 실행하고, 요청 데이터와 객체들은 메모리(RAM)를 오가요. JavaScript 코드가 아무리 많아도 한순간에 그 코드를 실행하는 core는 하나예요.

Step 7

데이터베이스에 다녀오기

주문을 저장하러 옆 서버의 Postgres에 다녀와요.

+8ms

논리
INSERT →COMMIT →← result
물리
  1. 앱 서버
  2. DB 서버(RDS)
  3. SSD/EBS
INSERT
COMMIT
result

요청이 가는 중: INSERT

주문을 저장하려면 데이터베이스에 다녀와야 해요. Node 서버는 connection poolDB 연결을 미리 여러 개 열어 두고 돌려 쓰는 방식이에요. 매번 새로 연결하는 시간을 아껴 줘요.에 미리 열어 둔 Postgres 연결 하나를 빌려서, 연결 인사 없이 바로 쿼리를 보내요.

Postgres는 INSERT를 실행하고, COMMIT 때 변경 내용을 WALWrite-Ahead Log데이터를 바꾸기 전에 변경 내용을 먼저 순서대로 적어 두는 기록이에요. 장애가 나도 이걸로 복구해요.에 적어 디스크에 안전하게 남은 것을 확인한 뒤에야 "성공"이라고 답해요. 앱 서버와 DB 서버는 같은 리전 안의 다른 서버라, 이 대화도 짧은 네트워크 왕복이에요.

+ 더 알아보기

Postgres는 HTTP가 아니라 자기만의 wire protocol프로그램끼리 네트워크로 주고받는 메시지의 형식과 순서를 정한 규칙이에요.로 대화해요. 쿼리를 받으면 SQL을 해석하고, 실행 계획을 세우고, 실제로 실행하는 순서로 처리해요.

RDS의 디스크는 서버에 꽂힌 SSD가 아니라 네트워크로 연결된 EBSElastic Block StoreAWS가 네트워크로 연결해 주는 디스크예요. 서버에 직접 꽂힌 디스크처럼 쓸 수 있어요.예요. 그래서 "디스크에 썼다"는 확인에도 데이터센터 안의 네트워크 왕복이 들어가요. Multi-AZ DB 인스턴스로 구성했다면 다른 가용 영역의 대기 서버에도 기록된 뒤에 답이 와요.

서버가 요청을 처리하는 시간 중에서는 이 단계가 가장 긴 경우가 많아요. 쿼리 실행, 디스크 기록, 네트워크 왕복이 모두 겹치기 때문이에요.

Step 8

답장이 화면에 그려지기까지

응답이 같은 길로 돌아오고, 브라우저가 화면을 다시 그려요.

+30ms

논리
← 201 Created← JSON.parse← render
물리
  1. 폰 화면
  2. GPU
  3. 기지국
  4. 통신사 코어망
  5. AWS 서울

응답이 돌아오는 중: 201 Created

핸들러가 주문 결과를 돌려주면 Node.js가 이를 JSON으로 바꿔 201 Created 응답을 만들어요. 응답은 ALB에서 다시 암호화되어 왔던 길을 거꾸로 달려 폰에 도착해요.

브라우저는 응답을 복호화하고 fetch의 Promise를 완료시켜요. 우리 코드가 받은 데이터로 화면 상태를 바꾸면, 브라우저가 바뀐 부분을 다시 그려서 "주문 완료"가 나타나요.

+ 더 알아보기

화면을 다시 그리는 데도 순서가 있어요. 바뀐 DOMDocument Object Model브라우저가 페이지를 다루기 위해 만든 요소들의 트리 구조예요.에 스타일을 계산하고, 위치와 크기를 정하고(layout), 픽셀로 칠한 뒤(paint), 여러 층을 GPUGraphics Processing Unit화면에 그릴 픽셀 계산을 빠르게 처리하는 프로세서예요.에서 합쳐요(composite).

완성된 화면은 디스플레이가 다음에 새로 고쳐지는 순간에 보여요. 60Hz 화면이면 약 16.7ms마다, 120Hz 화면이면 약 8.3ms마다 기회가 와요. 그래서 계산이 끝나도 화면에 보이기까지 조금 기다릴 수 있어요.