ubuntu@ip-172-31-0-63:~/application$ curl -s http://localhost:5000/test/1
1772889059.215698sudo apt update
sudo apt install siege -y
ubuntu@ip-172-31-0-63:~/application$ siege --version
New configuration template added to /home/ubuntu/.siege
Run siege -C to view the current settings in that file
SIEGE 4.0.7
Copyright (C) 2018 by Jeffrey Fulmer, et al.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.
ubuntu@ip-172-31-0-63:~/application$ siege -c 100 -b -t30s -q http://localhost:5000/test/1
{ "transactions": 2740,
"availability": 100.00,
"elapsed_time": 29.68,
"data_transferred": 0.05,
"response_time": 1.05,
"transaction_rate": 92.32,
"throughput": 0.00,
"concurrency": 96.61,
"successful_transactions": 2740,
"failed_transactions": 0,
"longest_transaction": 2.76,
"shortest_transaction": 0.33
}siege의 파라미터를 보면 -c는 동시에 요청할 사용자의 수를 설정하고 -b는 벤치마킹 모드로 동작하도록 설정하며 -t는 테스트하는 기간을 설정한다. 즉, 30초 동안 동시 요청 100개씩으로 테스트를 진행했다.
첫 번째 테스트 결과를 보면 초당 트랜잭션이 92.32로 측정되었다.
제일 먼저 CPU와 관련된 성능을 측정하고 최적화하는 방법을 살펴볼 것이다. 우리가 만든 애플리케이션이 과연 시스템이 제공하는 CPU 리소스를 최대한으로 이용하고 있는지, top 명령을 이용해서 확인해보자.
top - 13:22:48 up 5 days, 9:00, 1 user, load average: 4.39, 5.14, 4.45
Tasks: 148 total, 1 running, 147 sleeping, 0 stopped, 0 zombie
%Cpu(s): 44.2 us, 13.0 sy, 0.0 ni, 41.2 id, 0.0 wa, 0.0 hi, 0.0 si, 1.7 st
MiB Mem : 954.8 total, 80.1 free, 753.1 used, 285.0 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 201.7 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
463917 root 20 0 2936884 47344 34248 S 11.3 4.8 0:00.34 dotnet
463696 root 20 0 2936864 47368 34264 S 3.3 4.8 0:00.36 dotnet
925 root 20 0 2066016 101072 22916 S 2.7 10.3 13:07.54 dockerd
1 root 20 0 22780 10284 5892 S 1.0 1.1 0:59.88 systemd
184 root 20 0 27944 8444 3740 S 1.0 0.9 1:10.60 systemd-udevd
567 root 20 0 1893408 33936 11680 S 0.7 3.5 3:08.11 containerd
16555 root 20 0 1776576 26236 8332 S 0.7 2.7 0:51.91 snapd
230655 ubuntu 20 0 20364 8104 5956 S 0.7 0.8 0:14.04 systemd
463849 root 20 0 27948 7008 2268 S 0.7 0.7 0:00.02 (udev-worker)
463850 root 20 0 27948 7008 2268 S 0.7 0.7 0:00.02 (udev-worker)
15 root 20 0 0 0 0 I 0.3 0.0 0:18.80 rcu_sched
120 root 19 -1 66960 12416 11152 S 0.3 1.3 0:54.70 systemd-journal
508 message+ 20 0 9988 4868 3752 S 0.3 0.5 0:23.25 dbus-daemon
521 root 20 0 18080 5360 4268 S 0.3 0.5 0:04.74 systemd-logind
463422 root 20 0 1238392 14988 10732 S 0.3 1.5 0:00.01 containerd-shim
463865 root 20 0 27948 6900 2160 S 0.3 0.7 0:00.01 (udev-worker)
463961 root 20 0 27948 6900 2160 S 0.3 0.7 0:00.01 (udev-worker)
463965 root 20 0 27948 7008 2268 S 0.3 0.7 0:00.01 (udev-worker)
2 root 20 0 0 0 0 S 0.0 0.0 0:00.04 kthreadd
3 root 20 0 0 0 0 S 0.0 0.0 0:00.00 pool_workqueue_release
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/R-rcu_gp
5 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/R-sync_wq
6 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/R-kvfree_rcu_reclaim
7 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/R-slub_flushwq
8 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/R-netns
10 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:0H-events_highpri
13 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/R-mm_percpu_wq
14 root 20 0 0 0 0 S 0.0 0.0 0:10.00 ksoftirqd/0
16 root 20 0 0 0 0 S 0.0 0.0 0:00.00 rcu_exp_par_gp_kthread_worker/0
17 root 20 0 0 0 0 S 0.0 0.0 0:00.38 rcu_exp_gp_kthread_worker
18 root rt 0 0 0 0 S 0.0 0.0 0:02.11 migration/0
19 root -51 0 0 0 0 S 0.0 0.0 0:00.00 idle_inject/0
20 root 20 0 0 0 0 S 0.0 0.0 0:00.00 cpuhp/0
21 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kdevtmpfs
22 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/R-inet_frag_wq
23 root 20 0 0 0 0 I 0.0 0.0 0:00.00 rcu_tasks_rude_kthread top 결과를 보면 python 프로세스(여기서는 dockerd) 하나만 동작하고 있는 것을 볼 수 있다. 지금 사용하고 있는 플라스크의 내장 애플리케이션 서버는 싱글 스레드로 동작하기 때문에 당연한 결과일 것이다. 하지만 더 많은 프로세스로 요청 받으면 더 빠른 응답 속도를 낼 수 있지 않을까? 그래서 플라스크의 내장 애플리케이션 서버를 사용하지 않고 gunicorn이라는 별도의 애플리케이션 서버를 사용해보려고 한다.
ubuntu@ip-172-31-0-63:~/application$ vi Dockerfile
ubuntu@ip-172-31-0-63:~/application$ vi requirements.txt
ubuntu@ip-172-31-0-63:~/application$ docker-compose restart
Restarting application_flask_1 ... done
Restarting application_redis_1 ... done여기서 핵심이 되는 부분은 -w로 설정한 worker의 개수다. 5000번 포트로 바인딩되어 우리가 만든 app이라는 애플리케이션을 2개의 프로세스를 통해서 서비스하도록 설정했다.
ubuntu@ip-172-31-0-63:~/application$ siege -c 100 -b -t30s -q http://localhost:5000/test/1
{ "transactions": 3227,
"availability": 100.00,
"elapsed_time": 29.66,
"data_transferred": 0.05,
"response_time": 0.91,
"transaction_rate": 108.80,
"throughput": 0.00,
"concurrency": 98.54,
"successful_transactions": 3227,
"failed_transactions": 0,
"longest_transaction": 2.31,
"shortest_transaction": 0.22
}초당 트랜잭션 수를 보면 상당히 많은 성능 향상이 있음을 확인할 수 있다. 위 결과에서는 92정도를 기록하던 초당 트랜잭션이 108까지 늘어났다. 이는 gunicorn을 멀티 프로세스 모드로 동작시켰기 때문이기도 하지만, 그만큼 플라스크 기본 애플리케이션 서버의 성능이 좋지 않음을 의미하는 것이기도 하다.
애플리케이션이 동작하는 동안의 소켓 상태를 ss 명령으로 확인해보자.
ubuntu@ip-172-31-0-63:~$ ss -s
Total: 349
TCP: 4248 (estab 40, closed 4157, orphaned 0, timewait 4096)
Transport Total IP IPv6
RAW 1 0 1
UDP 7 5 2
TCP 91 85 6
INET 99 90 9
FRAG 0 0 0
ubuntu@ip-172-31-0-63:~$ netstat -napo | grep -i 6379
(No info could be read for "-p": geteuid()=1000 but you should be root.)
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN - off (0.00/0/0)
tcp6 0 0 :::6379 :::* LISTEN - off (0.00/0/0)위 결과를 보면 TIME_WAIT 상태의 소켓이 생성되고, 대부분의 소켓이 6379 포트, Redis 서버를 향해 있는 것을 볼 수 있다.
코드를 보면 사용자가 API를 호출한 순간 Redis 서버로의 연결을 만들고 요청이 완료된 순간 Redis 서버로의 연결이 자동으로 끊어진다. 7장에서 이야기한 것처럼 애플리케이션이 먼저 Redis 서버와의 연결을 끊기 때문에 다수의 TIME_WAIT 소켓이 발생한다. 그럼 TIME_WAIT 소켓을 없앨 수는 없을까? Redis 서버로의 요청이 잦기 때문에 사용자의 요청이 올 때마다 연결을 맺지 말고 미리 만들어 놓은 연결을 사용할 수는 없을까? 8장에서 살펴본 keepalive와 관련된 이야기이다. 소스 코드를 살짝 고쳐보자.
API 호출 시마다 연결했던 부분을 수정해서 미리 커넥션 풀을 만들어 놓고, 요청이 올 때는 그 커넥션 풀을 사용하는 방식으로 고쳤다.
ubuntu@ip-172-31-0-63:~$ ss -s
Total: 253
TCP: 4138 (estab 2, closed 4123, orphaned 0, timewait 4096)
Transport Total IP IPv6
RAW 1 0 1
UDP 7 5 2
TCP 15 9 6
INET 23 14 9
FRAG 0 0 0
ubuntu@ip-172-31-0-63:~$ ss -s
Total: 253
TCP: 4138 (estab 2, closed 4123, orphaned 0, timewait 4096)
Transport Total IP IPv6
RAW 1 0 1
UDP 7 5 2
TCP 15 9 6
INET 23 14 9
FRAG 0 0 0
ubuntu@ip-172-31-0-63:~$ ss -s
Total: 247
TCP: 4135 (estab 2, closed 4120, orphaned 0, timewait 4096)
Transport Total IP IPv6
RAW 1 0 1
UDP 7 5 2
TCP 15 9 6
INET 23 14 9
FRAG 0 0 0
ubuntu@ip-172-31-0-63:~$ netstat -napo | grep -i 6379
(No info could be read for "-p": geteuid()=1000 but you should be root.)
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN - off (0.00/0/0)
tcp6 0 0 :::6379 :::* LISTEN - off (0.00/0/0)
ubuntu@ip-172-31-0-63:~$ (나는 왜 똑같지..)
TIME_WAIT 소켓 수는 1800여개에서 700여 개로 줄었으며, 6379 포트는 더 이상 TIME_WAIT가 아닌 EST 상태로 네 개만 보인다. gunicorn 프로세스가 네 개이기 때문이다. 그렇다면 이번엔 성능이 어느 정도로 측정되었을까?
{ "transactions": 3447,
"availability": 100.00,
"elapsed_time": 29.16,
"data_transferred": 0.06,
"response_time": 0.83,
"transaction_rate": 118.21,
"throughput": 0.00,
"concurrency": 97.90,
"successful_transactions": 3447,
"failed_transactions": 0,
"longest_transaction": 1.84,
"shortest_transaction": 0.12
}{ "transactions": 6752,
"availability": 100.00,
"elapsed_time": 29.11,
"data_transferred": 0.11,
"response_time": 0.42,
"transaction_rate": 231.95,
"throughput": 0.00,
"concurrency": 98.43,
"successful_transactions": 6752,
"failed_transactions": 0,
"longest_transaction": 1.03,
"shortest_transaction": 0.11
}기존 118 → 231 수준으로 성능이 향상된 것을 확인할 수 있다. 요청마다 맺고 끊음을 반복하던 기존 코드에서 한번 맺은 세션을 계속 사용하는 코드로 수정되면서 TCP handshake에 대한 오버헤드가 줄어들고 성능이 나아진 것이다. 하지만 아직도 TIME_WAIT 소켓이 많다.
확인해보니 전부 테스트를 위해 유입된 소켓들이다. TIME_WAIT는 먼저 연결을 끊는 쪽에서 발생하는데, 그럼 애플리케이션이 먼저 연결을 끊었다는 이야기이다. 정말 그렇게 동작한걸까? 간단한 telnet 테스트를 통해서 확인해보자.
ubuntu@ip-172-31-0-63:~$ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /test/1 HTTP/1.1
HTTP/1.1 500 Internal Server Error
Connection: close
Content-Type: text/html
Content-Length: 141
<html>
<head>
<title>Internal Server Error</title>
</head>
<body>
<h1><p>Internal Server Error</p></h1>
</body>
</html>
Connection closed by foreign host.클라이언트의 GET 요청에 대한 응답을 줄 때 Connection: close 헤더를 내려준다. 이 헤더는 서버가 연결을 유지하지 않는다는 의미로 이를 통해 먼저 연결을 끊었음을 알 수 있다. 여러 개의 요청이 들어올 때는 더 나은 성능을 위해 연결을 유지해서 이전에 맺어 놓은 세션을 이용해야 한다고 7장과 8장에서 확인했다. 그럼 gunicorn 서버가 keepalive를 지원하기 위해서는 어떻게 해야할까?
root@836c75d4c5ff:/app# gunicorn -h
usage: gunicorn [OPTIONS] [APP_MODULE]
options:
-h, --help show this help message and exit
-v, --version show program's version number and exit
-c CONFIG, --config CONFIG
:ref:`The Gunicorn config file<configuration_file>`. [./gunicorn.conf.py]
-b ADDRESS, --bind ADDRESS
The socket to bind. [['127.0.0.1:8000']]
--backlog INT The maximum number of pending connections. [2048]
-w INT, --workers INT
The number of worker processes for handling requests. [1]
-k STRING, --worker-class STRING
The type of workers to use. [sync]
--threads INT The number of worker threads for handling requests. [1]
--worker-connections INT
The maximum number of simultaneous clients. [1000]
--max-requests INT The maximum number of requests a worker will process before restarting. [0]
--max-requests-jitter INT
The maximum jitter to add to the *max_requests* setting. [0]
-t INT, --timeout INT
Workers silent for more than this many seconds are killed and restarted. [30]
--graceful-timeout INT
Timeout for graceful workers restart in seconds. [30]
**--keep-alive INT The number of seconds to wait for requests on a Keep-Alive connection. [2]**결과를 보면 —keep-alive 라는 옵션을 통해서 설정할 수 있다. 해당 옵션을 이용해서 웹 서버를 다시 구동해보자.
ubuntu@ip-172-31-0-63:~/application$ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /test/1 HTTP/1.1
HTTP/1.1 500 Internal Server Error
Connection: close
Content-Type: text/html
Content-Length: 141
<html>
<head>
<title>Internal Server Error</title>
</head>
<body>
<h1><p>Internal Server Error</p></h1>
</body>
</html>
Connection closed by foreign host.위 결과를 보면 이번에도 Connection: close로 헤더를 내려주는 것을 볼 수 있다. --keep-alive 옵션을 주었지만 제대로 동작하지 않는 것처럼 보인다. 공식 홈페이지에서 gunicorn의 Keepalive에 대한 옵션을 찾아보면 다음과 같은 내용을 찾을 수 있다.
기본으로 사용하는 sync 타입 워커에서는 keepalive 기능을 사용할 수 없고, async 타입으로 동작하는 다른 워커를 사용해야만 keepalive 기능을 사용할 수 있다는 설명이다.
그러면 gunicorn에서 제공하는 async 타입의 워커 중에서 eventlet을 사용해서 웹 서버를 다시 띄워보자.
root@ip-172-31-0-209:/home/ubuntu/app# telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /test/1 HTTP/1.1
Connection closed by foreign host.(왜 나는 안되는겨 ㅋㅋㅋㅋㅋㅋㅋㅋㅋ)
이번엔 정확하게 의도대로 Connection: keep-alive 헤더가 내려오고 연결도 먼저 끊어지지 않았다. 그럼 이 상태에서는 어느 정도의 성능을 낼 것인지 확인해보자.
root@ip-172-31-0-209:/home/ubuntu/app# siege -c 100 -b -t30s -q -H "Connection: Keep-Alive" http://localhost:5000/test/1
New configuration template added to /root/.siege
Run siege -C to view the current settings in that file
{ "transactions": 7287,
"availability": 100.00,
"elapsed_time": 29.51,
"data_transferred": 0.12,
"response_time": 0.40,
"transaction_rate": 246.93,
"throughput": 0.00,
"concurrency": 99.21,
"successful_transactions": 7287,
"failed_transactions": 0,
"longest_transaction": 1.03,
"shortest_transaction": 0.02
}초당 트랜잭션이 260 정도이던 지난 테스트 결과와 달리 초당 트랜잭션이 거의 2000 가깝게 측정되었다.
root@ip-172-31-0-209:/home/ubuntu/app# netstat -napo | grep -i time_wait
tcp 0 0 172.31.0.209:38336 13.124.245.230:80 TIME_WAIT - timewait (54.09/0/0)
root@ip-172-31-0-209:/home/ubuntu/app# ss -s
Total: 192
TCP: 19 (estab 2, closed 7, orphaned 0, timewait 1)
Transport Total IP IPv6
RAW 1 0 1
UDP 6 4 2
TCP 12 6 6
INET 19 10 9
FRAG 0 0 0 또한 서버쪽에서도 더 이상 5000번 포트에 대한 TIME_WAIT 소켓이 생성되지 않는다. Keepalive를 사용하도록 헤더가 들어온 덕분에 먼저 끊지 않았기 때문이다.
이번 테스트에서는 Keepalive의 효과도 컸지만 gunicorn 의 동작 방식을 기본 sync 워커에서 eventlet async 워커로 변경해서 사용한 것도 성능 향상에 많은 기여를 했다.
대부분으 경우 gunicorn이나 uwsgi와 같은 애플리케이션 서버가 직접 사용자 요청을 받도록 설정하지 않는다. 여러 가지 이유가 있겠지만 nginx, apache와 같은 프론트 서버를 두는 것이 보안 설정에도 유리하고 virtual host, server_name을 통한 다양한 라우팅이 가능하기 때문이다. 여기서도 gunicorn 앞단에 nginx를 설정해서 80 포트를 통해 서비스하도록 해보자.
root@ip-172-31-0-209:/etc/nginx# curl -s http://localhost:5000/test/1
1773571966.4128993
root@ip-172-31-0-209:/etc/nginx# curl -s http://localhost/test/1
1773571976.6972203
root@ip-172-31-0-209:/etc/nginx# root@ip-172-31-0-209:~# siege -c 100 -b -t30s -q -H "Connection: Keep-Alive" http://localhost/test/1
{ "transactions": 7257,
"availability": 100.00,
"elapsed_time": 29.83,
"data_transferred": 0.12,
"response_time": 0.41,
"transaction_rate": 243.28,
"throughput": 0.00,
"concurrency": 98.97,
"successful_transactions": 7257,
"failed_transactions": 0,
"longest_transaction": 0.99,
"shortest_transaction": 0.03
}네 번째 테스트 결과보다 초당 트랜잭션이 조금 떨어졌다. (246 → 243) 애플리케이션 서버와 바로 붙지 않고 nginx를 거치기 때문에 당연한 결과라고 볼 수 있다. 하지만 더 중요한 것은 Availability가 99.35%라는 것인데, 이는 일부 요청이 실패했다는 의미이다. nginx의 error_log에 어떤 로그가 남았는지 확인해보자. (나는 100%라 아무 로그도 안 남았음)
결과를 보면 Cannot assign requested address 라는 에러를 확인할 수 있다. nginx가 gunicorn으로 사용자의 요청을 전달할 때 사용할 로컬 포트를 할당 받지 못했음을 의미한다. 7장에서도 서도 이야기 했지만 TIME_WAIT 상태의 소켓은 tcp.ipv4.tw_reuse 옵션을 켜지 않으면 타이머가 끝나서 반환될 때까지 사용할 수 없다.
netstat 으로 확인해보면 위와 같은 소켓들이 다수 생성되어 있다. 이 소켓들이 바로 nginx에서 gunicorn으로 보낼 때 사용하는 소켓들이다.
net.ipv4.tcp_tw_reuse 커널 파라미터를 enable로 설정해서 재사용할 수 있게 하거나, nginx와 gunicorn 사이에서도 keepalive로 동작할 수 있게 하면 TIME_WAIT 소켓을 해결할 수 있다.
root@ip-172-31-0-209:/var/log/nginx# sysctl -w net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_tw_reuse = 1
root@ip-172-31-0-209:/var/log/nginx# siege -c 100 -b -t30s -q -H "Connection: Keep-Alive" http://localhost/test/1
{ "transactions": 6845,
"availability": 100.00,
"elapsed_time": 29.59,
"data_transferred": 0.12,
"response_time": 0.43,
"transaction_rate": 231.33,
"throughput": 0.00,
"concurrency": 98.97,
"successful_transactions": 6845,
"failed_transactions": 0,
"longest_transaction": 1.20,
"shortest_transaction": 0.02
}이번엔 100% 모든 요청을 다 처리했다. 하지만 아직도 많은 TIME_WAIT 소켓들이 남아 있다. 8장에서도 이야기한 것처럼 nginx는 upstream keepalive라는 기능이 있어서 gunicorn과 같이 upstream 서버와 keepalive 연결을 열어놓고 서비스할 수 있다. 이 기능을 사용하기 위해 아래와 같이 Nginx.conf를 변경한다.
HTTP/1.1은 기본적으로 **지속 연결(persistent connections)**을 사용한다.
이 방식은 하나의 TCP 연결에서 여러 개의 요청(request)과 응답(response)을 처리할 수 있도록 한다.
HTTP 구현체는 지속 연결을 지원하는 것이 권장된다(SHOULD).
수신자(클라이언트 또는 서버)는 가장 최근에 받은 메시지의 프로토콜 버전과 Connection 헤더 필드(HTTP 문서 Section 7.6.1)를 기반으로 해당 연결이 지속되는지 여부를 판단한다.
판단 규칙은 다음과 같다.
현재 메시지에 "close" 연결 옵션이 포함되어 있으면
(Section 9.6)
현재 응답 이후 연결은 유지되지 않는다.
받은 프로토콜이 HTTP/1.1 또는 그 이후 버전이면
현재 응답 이후에도 연결이 유지된다.
다음 조건이 모두 만족되면 연결은 유지된다.
"keep-alive"연결 옵션이 존재- 수신자가 프록시가 아니거나, 메시지가 응답 메시지
- 수신자가 HTTP/1.0의 keep-alive 메커니즘을 지원하기로 결정한 경우
이 경우
현재 응답 이후에도 연결이 유지된다.
현재 응답 이후 연결은 종료된다.
지속 연결을 지원하지 않는 클라이언트는 모든 요청 메시지에 반드시 "Connection: close" 옵션을 포함해야 한다.
지속 연결을 지원하지 않는 서버는 1xx (정보성 응답)를 제외한 모든 응답 메시지에 "Connection: close" 옵션을 포함해야 한다.
클라이언트는 다음 조건 중 하나가 발생할 때까지 지속 연결에서 추가 요청을 보낼 수 있다.
"Connection: close"옵션을 보내거나 받았을 때"keep-alive"옵션이 없는 HTTP/1.0 응답을 받았을 때
지속 연결을 유지하려면 연결 위에서 전송되는 모든 메시지는 스스로 메시지 길이를 정의할 수 있어야 한다.
즉 연결 종료를 통해 메시지 길이를 판단하는 방식이 아니어야 한다.
(자세한 내용은 Section 6 참조)
서버는 응답을 보낸 후 다음 두 가지 중 하나를 반드시 해야 한다.
- 요청 메시지 본문 전체를 읽는다
- 연결을 종료한다
그렇지 않으면 지속 연결에서 남아있는 데이터가 다음 요청으로 잘못 해석될 수 있다.
마찬가지로 클라이언트도 같은 연결을 재사용하려면 응답 메시지 본문 전체를 읽어야 한다.
프록시 서버는 HTTP/1.0 클라이언트와 지속 연결을 유지해서는 안 된다.
이는 많은 HTTP/1.0 클라이언트가 구현한 "Keep-Alive" 헤더와 관련된 문제 때문이며, 자세한 내용은 Appendix C.2.2를 참고한다.
HTTP/1.0 클라이언트와의 **하위 호환성(backwards compatibility)**에 대한 추가 정보도 Appendix C.2.2에서 확인할 수 있다.
💡 참고로 이 RFC 부분은 지금 질문하신 것과 정확히 연결됩니다.
특히 이 문장이 핵심입니다.
If the received protocol is HTTP/1.1, the connection will persist
즉
HTTP/1.1 기본 = keepalive
Connection: close = 예외
입니다.
- HTTP/1.1부터는
- 이미 연결되어 있는 TCP 연결을 재사용하는
- Keep-Alive라는 기능을 Default로 지원한다.
- 즉 Handshake 과정이 생략되므로 성능 향상을 기대 할 수 있다.
- 아래의 예는 HTTP 1.1 Keep-Alive 기능에 대해글에서 발췌했다.