-
고성능 웹 서버를 만들기 위해 기존에는 select(), poll(), epoll 등 다양한 이벤트 기반 모델이 사용됨
- 하지만 이러한 시스템콜들의 성능 한계로 io_uring이 등장, 요청을 큐에 넣어 커널이 비동기로 처리하는 방식을 도입함
-
kTLS는 커널이 TLS 암호화 처리를 담당, sendfile() 사용 가능성과 하드웨어 오프로딩 등 추가적인 최적화가 가능해짐
-
Descriptorless files의 도입으로 파일디스크립터를 직접적으로 전달하지 않으면서 io_uring에 최적화된 접근 방식 제공함
- Rust, io_uring, kTLS를 결합한 tarweb 오픈소스 프로젝트를 통해 요청별로 추가 시스템콜 없이 HTTPS를 제공, 안전성과 메모리 관리에 관한 이슈도 논의함
고성능 웹 서버 구조의 진화
- 2000년대 초반부터 고용량 웹 서버에 대한 요구가 증가함
- 초기에는 각 요청마다 새로운 프로세스를 생성하는 방식이 일반적이었으나, 이는 높은 비용 문제로 인해 preforking 기법이 등장함
- 이후 스레드 도입 및 select(), poll() 활성화를 거쳐 컨텍스트 스위칭 비용을 줄이는 방식으로 발전함
- 다만, select()와 poll() 방식도 연결 수가 많아질수록 커널에 큰 배열을 빈번히 전달해야 하므로 확장성에 한계가 존재함
epoll의 등장
- Linux 환경에서는 epoll이 도입되어 기존 방식보다 효율적인 다중 연결 처리가 가능해짐
- epoll은 변경점(델타)만 처리하여 불필요한 리소스 소모를 줄임
- 모든 시스템콜이 완전히 없어지지는 않지만, 비용이 상당히 줄어듦
io_uring 개요
-
io_uring은 각 요청마다 시스템콜을 호출하는 대신, 커널이 비동기적으로 처리할 수 있도록 요청을 메모리 상 큐에 추가함
- 예를 들어, accept()를 큐에 넣어두면 커널이 처리 후 완료 큐에 결과를 반환함
- 웹서버는 큐에 요청을 추가하고, 결과는 별도 메모리 영역에서 확인하는 방식으로 작동함
- 바쁜 루프(busy loop)를 피하기 위해, 큐에 변화가 없으면 웹서버와 커널 모두 필요한 경우에만 시스템콜을 호출하여 절전 효과를 얻음
- 적절한 라이브러리를 활용하면, 활성화된 서버는 요청 처리 중 별도의 시스템콜 없이 작동 가능함
멀티 코어와 NUMA 환경
- 현대 CPU의 다중 코어 환경을 고려해 코어별 단일 스레드 실행 및 데이터 구조의 공유를 최소화하는 전략이 유효함
- NUMA 환경에서는 각 스레드가 자신의 로컬 노드 메모리에만 접근하여 최적화
- 요청 분배의 완벽한 균형은 추가 연구가 필요함
메모리 할당
- 커널과 웹서버 모두에서 메모리 할당이 남아있으며, 사용자 공간에서의 할당도 결국 시스템콜로 연결됨
- 웹서버 단에서는 연결당 고정 크기의 메모리 블록을 미리 할당하여 파편화 및 부족 문제를 예방함
- 커널 측에서도 연결별로 입출력 버퍼가 필요하며, 소켓 옵션 등으로 일부 조정 가능함
- 메모리 부족 현상 발생 시 심각한 장애로 이어질 수 있음
kTLS(커널 TLS) 소개
-
kTLS는 Linux 커널에서 암호화 및 복호화 연산을 담당하는 기능임
- 핸드셰이크는 애플리케이션에서 처리하지만, 그 이후로는 커널이 순수 텍스트처럼 데이터 전송을 처리함
- sendfile() 사용이 가능해져 유저-커널 공간 간 메모리 복사를 줄일 수 있음
- 네트워크 카드가 지원할 경우, 암호화 연산까지 하드웨어에 오프로딩할 수 있는 이점이 있음
Descriptorless Files
- 사용자 공간에서 커널 공간으로 파일디스크립터를 직접 전달할 때 발생하는 오버헤드를 줄이기 위해 등장한 방식임
- register_files를 이용해 io_uring에만 유효한 별도의 '정수' 파일번호를 사용하며, /proc/pid/fd에는 표시되지 않음
- 시스템의 ulimit 제한은 여전히 적용됨
tarweb 프로젝트 소개
-
tarweb은 위 모든 기술을 적용한 예시 웹서버 오픈소스 프로젝트임
- 단일 tar 파일 내용을 제공하는 구조로, Rust, io_uring, kTLS 등 최신 고성능 기술이 결합되어 있음
- 실사용 과정에서 io_uring과 kTLS의 호환성 문제(setsockopt 미지원 등)가 있어 Pull Request로 일부 이슈를 해결함
- 프로젝트는 아직 미완성 단계이며, Rust의 rustls 라이브러리가 핸드셰이크 과정에서 메모리 할당을 수행할 수 있음
- 핵심은 각 요청별 추가 시스템콜 없이 HTTPS 서비스가 가능하다는 점임
벤치마크 및 성능 측정
- 저자는 아직 충분한 벤치마크를 진행하지 않았으며, 코드 정비 후 성능 테스트 예정임
io_uring과 Rust의 안전성 문제
- 동기식 시스템콜과 달리, io_uring에서는 완료 이벤트 이전까지 메모리 버퍼가 해제되지 않아야 함
- io-uring 크레이트는 Rust의 컴파일 타임 안전성을 보장하지 않으며, 런타임 체크도 부족함
- 잘못 사용 시 C++과 유사하게 심각한 문제까지 이어질 수 있어, Rust 본연의 안전성이 약화됨
- pinning과 borrow checker를 적극적으로 활용하는 별도의 safer-ring 크레이트가 필요함
- 이 문제는 이미 커뮤니티에서 논의 중임
참고 및 추가 링크
- 본 내용은 2025-08-22 기준 HackerNews에서 논의된 포스트임