TrotVote
[IT 심층 분석]

네트워크 고부하 환경에서 SoftIRQ가 CPU를 독점하는 문제와 RSS/RPS 구성으로 완벽 분산 처리

김민준 · IT 시스템 엔지니어|
수백만 명이 동시에 사용하는 실시간 게임 서버의 네트워크 레이어를 점검하던 중 매우 기이한 CPU 불균형 현상을 목격했습니다. 멀티코어 서버임에도 불구하고 모든 코어가 균일하게 바쁜 것이 아니라 오직 CPU 0번 코어 한 개만 100% 풀로드 상태이고 나머지 코어들은 한가롭게 놀고 있는 절름발이 구도가 펼쳐진 것입니다. top 명령어에서 si 즉 소프트웨어 인터럽트 항목이 유독 0번 코어에서만 치솟아 있는 것을 발견했고 이것이 소프트 인터럽트 핸들러인 ksoftirqd 스레드가 특정 코어에 몰리는 병목의 원인임을 직감했습니다. 리눅스 커널은 기본적으로 네트워크 인터페이스 카드(NIC)로부터 들어오는 패킷 인터럽트를 단 하나의 CPU 코어에서만 처리합니다. NIC 드라이버가 패킷을 수신해서 하드 인터럽트를 올리고 이것을 SoftIRQ 메커니즘을 통해 소프트웨어 단에서 후처리하는 모든 과정이 0번 코어에 집중되도록 기본값이 설정되어 있기 때문입니다. 수십만 개의 소규모 게임 패킷이 초당 폭풍처럼 밀려드는 이 서비스 환경에서 그 모든 처리 부담을 코어 하나에 떠넘기는 것은 공장 라인을 단 한 명의 작업자에게 책임지우는 것과 다를 바 없는 설계적 실수였습니다. 해결을 위해 두 가지 병렬 전략을 구사했습니다. 첫 번째는 하드웨어 수준의 RSS(Receive Side Scaling)를 활성화하는 것이었습니다. 이 기능이 지원되는 고급 NIC는 내부에 여러 개의 하드웨어 큐를 가지고 있으며 수신되는 패킷의 해시값을 기준으로 서로 다른 하드웨어 큐에 패킷을 분산 배치하게 됩니다. 각 하드웨어 큐가 서로 다른 CPU 코어에 매핑되어 인터럽트 자체를 하드웨어 단에서부터 균등 분배하는 것입니다. 현재 서버의 NIC가 RSS를 지원함을 확인하고 /proc/irq 하위의 smp_affinity 설정을 통해 각 하드웨어 큐 인터럽트가 개별 코어에 매핑되도록 수동으로 고정했습니다. 두 번째는 RSS를 지원하지 않는 구형 NIC나 저가 서버를 위한 소프트웨어 방식의 RPS(Receive Packet Steering) 설정이었습니다. /sys/class/net 경로 하위에 있는 rps_cpumap 파일에 패킷 처리에 참여시킬 CPU 코어의 비트마스크를 기록하면 커널이 소프트웨어 레벨에서 패킷 흐름의 해시를 계산해 여러 코어로 작업을 골고루 나눠주게 됩니다. 두 가지 방법을 혼용하여 적용한 뒤 CPU 활용률 그래프를 다시 들여다보자 마치 이전에 독불 장군처럼 혼자 타오르던 CPU 0번의 사용률이 낮아지면서 전체 코어들이 매우 균형있게 할당량을 나눠 갖는 건강한 멀티코어 서버의 모습이 눈앞에 펼쳐졌습니다.