TCP 커넥션 생성
크롬과 같은 브라우저 주소창에 URL을 입력하면 브라우저는 IP주소를 알아내기 위해 호스트명을 추출하고, 또한 포트번호를 추출하여 IP주소와 포트번호로 해당 서버에 TCP 커넥션을 생성한다.
TCP의 역할
TCP는 전송하고자 하는 데이터를 세그먼트라 불리는 조각 단위로 나눠 IP 패킷에 담아 전송한다. 이때 전송하고자 하는 데이터는 HTTP 요청 방식에 따라 차이가 있겠지만 '요청 메서드', '요청 URL', 그 외 '요청 헤더'에 담기는 각종 정보들이다. 결론적으로 IP 패킷 내 전송을 위한 정보를 담은 'IP 헤더'에 'TCP 세그먼트'가 동봉되어 전송되는 셈이다.
TCP 세그먼트에는 데이터 스트림 말고도 서버와 커넥션을 맺고, 소통하기 위한 정보가 담겨있다. 그 중 SYN, ACK와 같은 예약어를 통해 서버에 '커넥션 요청 보냄', '요청 승인 받았음', '데이터 전송했음'과 같은 형태의 소통을 하고, 체크섬을 통해 세그먼트의 손상이 있는지(비트 오류가 있는지) 검사한다.
HTTP 트랜잭션
브라우저 사용자에 해당하는 클라이언트와 서버 간 TCP 커넥션을 맺은 뒤 HTTP 요청을 보내서 응답을 받은 뒤 커넥션을 종료하기까지의 과정을 하나의 트랜잭션으로 본다.
기본적인 HTTP 트랜잭션의 과정을 세부적으로 살펴보면 아래와 같다.
- 서버에서 새로운 소켓을 만들어 80번 포트로 소켓을 묶는다.
- 소켓 커넥션 허가를 위한 리스닝을 시작하고 기다린다.
- 클라이언트는 URL에서 IP와 포트를 획득하여 이를 기반으로 TCP 커넥션을 시도한다.
- 서버에서 커넥션이 허용되었음을 통지한다.
- 클라이언트는 허용을 확인했다는 메시지와 함께 요청 정보를 보낸다.
- 서버에서 요청에 따른 처리 후 응답을 보낸다.
- 클라이언트에서 받은 응답을 처리한다. (브라우저 화면에 보여주거나, 특정 파일을 다운 받는 등등)
- 클라이언트와 서버가 각각 커넥션을 종료한다. 커넥션 종료 시점은 설정에 따라 다를 수 있다.
위 절차에서 클라이언트와 서버 간 커넥션을 맺는 단계를 '커넥션 핸드셰이크'라고 부른다.
트랜잭션의 지연
위 과정에서 트랜잭션의 지연을 일으킬 여러가지 요소가 과거부터 있었고, 어떤 방식으로 해결되고 있는지 알아보자.
TCP 커넥션 핸드셰이크 지연
과거에는 클라이언트는 서버에 요청을 보낼 때 마다 TCP 커넥션을 요청하도록 설계되어 있었다. 그렇다보니 작은 크기의 데이터를 전송할 경우에도 핸드셰이크를 우선적으로 수행해야 했고 이는 하나의 IP 패킷만 전송하면 될 일도 커넥션 때문에 두 개를 전송해야 하는 상황이고, 미미하지만 오버헤드로 인한 지연을 발생시키는 것에 해당했다.
핸드셰이크 지연 문제 해결을 위한 커넥션 유지 전략
위 문제를 해결하고자 커넥션을 유지하는 기능이 추가되었는데, HTTP 1.0+ 프로토콜에서 등장한 것이 Keep-Alive 기능이다. 이는 Keep-Alive라는 헤더를 전송하여 지속 시간을 설정하거나 트랜잭션 횟수에 따른 지속 등 설정된 옵션에 따라 정해진 만큼 커넥션을 유지하도록 하는 전략을 취했다고 한다.
이후 1.1 프로토콜에서는 Keep-Alive 기능을 명세에서 제외하고 아예 디폴트로 커넥션을 지속하도록 하는 '지속 커넥션' 전략이 적용되었다. 이는 Connection: close 헤더를 명시할 경우에 커넥션이 종료되도록 변경되었다.
하지만 Keep-Alive도 지속 커넥션도 모두 불안정한 요소가 있는 모양이다. 'HTTP 완벽가이드' 도서에 따르면 Keep-Alive가 옵션대로 잘 동작하리라는 보장이 없다고 하며, 1.1의 Connection: close 헤더를 명시하지 않는 것이 서버가 커넥션을 영원히 유지하겠다는 의미가 되지도 않는다고 한다. 그도 그럴 것이 커넥션을 계속 유지하는 것이 성능에 영향을 준다는 점, 과도한 요청 때문에 다면적인 측면에서 볼 때 자의적 또는 타의적으로 커넥션이 종료될 여지가 있기 때문이다.
확인 응답 지연
TCP 세그먼트에는 수신자가 세그먼트를 잘 받았음을 응답하는 '확인 응답' 패킷을 전송한다. 확인 응답을 통해 패킷이 문제 없이 잘 전송되었는가의 여부를 서로 판단한다. 이 때 확인 응답의 크기가 작아서 전송하고자 하는 데이터 패킷에 편승시켜 IP패킷이 두 번 갈일을 한 번 가도록 하기 위한 전략을 취한다. 이러한 알고리즘은 전송할 데이터 패킷이 나타날 때까지 확인 응답을 일정 기간 동안 기다리게 하는데(버퍼에 저장) 그 기간이 지나면 별도의 패킷을 만들어 전송하게 한다. 문제는 전송할 데이터 패킷을 찾지 못할 때 그 시간이 고스란히 지연 시간이 된다는 점이다. 발신자의 입장에서 데이터 전송의 안정성을 보장하기 위해 잘 받았다는 확인 응답을 기다린 후에 다음 데이터를 전송할 수도 있기 때문이다. (이는 확인 응답 지연 알고리즘의 경우에 해당한다.)
TCP의 Slow Start
TCP의 데이터 전송 속도는 커넥션 지속 시간이 오래 유지됨에 따라 제한 속도를 점차적으로 높여가는 전략을 취한다. 이는 갑자기 커넥션이 증가하면서 대량의 데이터를 쏟아부음으로 인한 부하를 방지하기 위한 수단이다. 제한 속도를 늘려간다는 것은 한 번에 전송 가능한 패킷의 수를 점차 늘려가는 방식을 말한다. 그래서 이제 막 생성된 커넥션의 전송 속도는 느릴 수 밖에 없다. 해당 문제는 과거에 이슈가 되었던 지연 현상으로 보여지며 현재는 커넥션을 지속하는 기술이 기본적으로 동작하기 때문에 신규 커넥션 생성 빈도수가 낮아졌다.
TCP 커넥션의 성능 향상을 위한 전략
병렬 커넥션
여러 개의 커넥션을 통해 동시에 요청을 처리하는 전략이다. 하나의 웹페이지에 접속했을 때 HTML와 관련된 모든 컴포넌트를 병렬로 요청하여 받을 수 있기 때문에 하나의 커넥션을 맺어 순차적으로 요청을 처리하는 것보다 빠를 수 있다. 하지만 서버의 성능을 고려하여 병렬 커넥션의 갯수를 제한할 필요가 있다. 커넥션은 메모리를 차지한다. 즉 서버에 동시 접속 가능자 수에 영향을 주게 되는데 클라이언트 단 한 명에게 만약 4개의 커넥션을 제공한다면 이는 다른 3명의 클라이언트의 자리까지 내어 준다는 얘기가 된다. 하지만 1:1 커넥션을 유지하여 병렬 커넥션 만큼의 전송 속도를 유지할 수 없다는 개개인의 사용 경험에 부정적일 수도 있다. 그러므로 적정선이 필요한 것이다.
병렬 커넥션과 지속 커넥션
앞서 살펴본 바와 같이 지속 커넥션은 Slow-Start문제를 해결한다는 장점이 있어 병렬 커넥션과 함께 사용한다.
파이프라인 커넥션
지속 커넥션이 확인되면 파이프라인을 구축하여 클라이언트가 요청에 대한 응답을 기다리는 동안 다음 요청을 큐에 쌓아두면서 계속 요청을 보내게 되는 전략이다. 이렇게 되면 기존 방식처럼 다음 요청을 위해 응답을 기다리는 시간을 절감할 수 있고, 여러 개의 요청을 묶어서 두 번 갈 것 한 번에 가도록 할 수 있어 패킷 전송 횟수를 줄일 수도 있다. 또한 큐에 적재하므로 서버에서 받은 요청을 다 처리하지 못했다 하더라도 클라이언트에서 응답 받지 못한 요청에 대해 재전송을 할 수 있게 된다.
'백엔드 개발자(node.js)가 되는 과정' 카테고리의 다른 글
JWT와 토큰 생성, 강제 로그아웃에 대하여 (0) | 2024.02.14 |
---|---|
데이터베이스의 1:N 관계에서 N+1 문제가 무엇인가? (0) | 2024.02.08 |
var, let, const의 차이와 호이스팅, 스코프 (0) | 2024.02.08 |
Redis에 대해서 살펴보자 (0) | 2024.02.07 |
HTTP와 Kafka를 통한 MongoDB 도큐먼트 생성 비교 (부하테스트) (0) | 2024.01.31 |