서민구님의 Linux Kernel 3.9 에서의 SO_REUSEPORT 글을 읽고 좀 더 자세히 살펴보았다.
원문은 Linux 3.9 introduced new way of writing socket servers임
리눅스 커널 3.9에 소켓 옵션 SO_REUSEPORT를 이용한 멋진 기능이 추가되었는데, 바로 같은 호스트(IP주소)의 같은 포트에 여러 개의 리스닝 소켓(서버 소켓)을 연결(bind)할 수 있다는 것이다. 이미 SO_REUSEADDR 옵션을 이용해서 TIME-WAIT 상태에 있는 소켓을 같은 주소로 재사용할 수 있었고 이는 매번 소켓을 다른 포트에 연결해야 하는 불편을 해소하고 재사용을 통한 효율을 높이기 위한 우회방법(workaround)일 뿐이었다. 하나의 주소/포트에 여러 번 연속해서 연결할 수 있을 뿐, 동시에 여러 쓰레드가 하나의 주소/포트를 공유해서 소켓을 여러 개 연결하지 못하는 한계점이 분명했다.
반면, SO_REUSEPORT 옵션을 이용하면 동시에 여러 쓰레드가 각각 소켓을 만들어서 동일한 서버 주소/포트에 연결할 수 있다는 장점이 있어서 소켓 응용프로그램을 작성할 때 상당한 편의성을 얻을 수 있다. 그러나 오래 전부터 사용할 수 있었지만 POSIX 표준이 아니었고 보안 상의 이슈로 널리 채택되지 못하고 있었다.
리눅스 커널 3.9에서는 이 옵션을 패치하여 멀티 코어 시스템에서 실행되는 멀티쓰레드 네트웍 서버 응용프로그램의 성능을 향상시키는 효과를 보게 되었다.
전통적인 네트웍 서버는 하나의 listener가 실행되고 들어오는 연결을 받아서 다른 worker thread에게 넘겨주는 방식이었는데, 이 옵션을 사용하게 되면 여러 쓰레드가 하나의 소켓을 공유하고 동시에 listener/worker 역할을 분담해서 수행하는 방식이 된다. 전자의 경우 listener가 병목이 될 가능성이 높지만 후자의 경우 4만 connection/second 정도의 성능까지 얻을 수 있었다고 한다. (보통 전자의 경우 workload에 따라 다르지만 수천~1만 connection/second임)
또 다른 전통적인 네트웍 서버의 방식으로 여러 쓰레드가 동시에 accept 상태에 들어가는 방식이 있는데, block되어 있다가 wake-up될 때의 분포가 불균형이 심해서 구글에서는 3배 정도 차이가 나는 것으로 관측했다고 한다. 이러면 당연히 CPU utilization이 낮아지게 된다.
while (1) {
new_fd = accept(...);
process_connection(new_fd);
}
이번에 도입된 SO_REUSEPORT 옵션은 커널이 이런 불균형을 해소해주기 때문에 멀티 쓰레드를 이용해 CPU utilization을 확보했다고 한다.
이번 패치에 또 다른 두 가지 주목할만한 점이 있다. 하나는 구현이 편해졌다는 것인데, 서버/클라이언트의 IP/port를 가지고 해시하기 때문에 클라이언트가 소켓을 재활용하면 같은 서버가 할당되는 효과가 있어서 상태를 유지하는(stateful) 대화를 구현하기가 쉬워졌다는 의미이다.
또 다른 하나는 이 옵션의 오래된 보안 결함이 아직 해결 중이라는 것이다. 한 포트에 연결된 서버 소켓의 갯수가 변화하면 새로 들어오는 연결이 맺어지지 않는(drop) 경우가 있는데 이건 3 way handshake에서 SYN 패킷과 최종 ACK 패킷이 동일한 소켓에 전달되지 않는 버그 때문이다. 여러 소켓 사이에 공유되는 연결 요청 테이블을 구현하는 방식으로 해결 중이다.