본문 바로가기
서버연구 블로그 서버연구 블로그

Context Switch가 많아지면 서버 성능이 떨어지는 이유

읽는 시간 약 17분

서버의 CPU 사용률이 높으면 성능이 떨어진다는 것은 쉽게 이해할 수 있습니다.

그런데 실제 운영 환경에서는 CPU 사용률이 100%가 아닌데도 응답 속도가 느려지는 경우가 있습니다. CPU가 50~70% 정도밖에 사용되지 않는데 API 응답 시간이 증가하거나, 처리량이 기대만큼 나오지 않는 상황입니다.

이때 확인해야 할 지표 중 하나가 **Context Switch(컨텍스트 스위치)**입니다.

Context Switch는 CPU가 현재 실행하던 프로세스나 스레드를 멈추고 다른 작업을 실행하기 위해 실행 상태를 전환하는 과정입니다. Linux 서버에서 반드시 필요한 정상적인 동작이지만, 지나치게 많이 발생하면 CPU가 실제 업무보다 작업을 바꾸는 데 더 많은 비용을 사용하게 됩니다.

특히 동시 접속자가 많은 웹 서버, 스레드 기반 애플리케이션 서버, 데이터베이스 서버에서는 Context Switch 증가가 처리량 저하와 지연시간 증가의 원인이 될 수 있습니다.

이번 글에서는 Context Switch가 왜 발생하는지부터 Linux Kernel 내부에서 어떤 작업이 이루어지는지, 그리고 운영 서버에서 어떻게 분석하고 개선해야 하는지까지 살펴보겠습니다.


Context Switch가 증가하는 원인

CPU는 동시에 무한히 많은 작업을 실행할 수 없습니다.

예를 들어 8개의 논리 CPU를 가진 서버에서 100개의 실행 가능한 스레드가 존재한다고 가정해보겠습니다.

실제로 한 순간에 CPU에서 실행될 수 있는 스레드는 최대 8개입니다. 나머지 스레드는 실행 순서를 기다려야 합니다.

Linux Kernel의 Scheduler는 여러 태스크에 CPU 시간을 배분합니다.

대략적인 흐름은 다음과 같습니다.

Thread A 실행
      ↓
스케줄링
      ↓
Thread B 실행
      ↓
스케줄링
      ↓
Thread C 실행

문제는 실행 가능한 스레드가 지나치게 많아졌을 때입니다.

CPU는 계속해서 실행 대상을 변경해야 합니다.

Context Switch가 증가하는 대표적인 원인은 다음과 같습니다.

  • CPU 코어 수보다 지나치게 많은 실행 스레드
  • 과도한 멀티스레드 설정
  • 짧은 작업을 수행하는 프로세스의 반복 생성
  • Lock 경쟁
  • 빈번한 I/O 대기와 Wake-up
  • 높은 인터럽트 발생
  • CPU 자원 부족
  • 애플리케이션의 잘못된 Worker 설정

따라서 Context Switch 자체가 문제라기보다는 왜 CPU가 이렇게 자주 실행 대상을 변경하고 있는가를 찾아야 합니다.


Context Switch가 발생할 때 CPU 내부에서는 무엇이 일어날까?

Context Switch를 단순히 프로그램 A에서 프로그램 B로 바꾸는 과정이라고 생각하기 쉽습니다.

하지만 실제로는 그보다 많은 작업이 필요합니다.

CPU에서 Thread A가 실행되고 있다고 가정해보겠습니다.

CPU
 │
 ├─ Thread A 실행
 │
 ├─ Thread A 상태 저장
 │
 ├─ Scheduler 실행
 │
 ├─ Thread B 상태 복원
 │
 └─ Thread B 실행

CPU가 Thread A 실행을 중단하면 Kernel은 이후 다시 실행할 수 있도록 필요한 실행 상태를 보존해야 합니다.

여기에는 CPU 레지스터, 프로그램 실행 위치 등 문맥을 구성하는 정보가 포함됩니다.

이후 Scheduler가 다음에 실행할 태스크를 결정하고 새로운 태스크의 실행 상태를 준비합니다.

이 과정에는 시간이 필요합니다.

더 중요한 것은 CPU 캐시 효율까지 영향을 받을 수 있다는 점입니다.

CPU는 메인 메모리보다 훨씬 빠른 L1, L2, L3 Cache를 사용합니다.

Thread A가 반복적으로 사용하던 데이터가 CPU Cache에 올라와 있다고 생각해보겠습니다.

CPU Cache

Thread A Data
Thread A Code
Thread A Working Set

그런데 CPU가 Thread B를 실행하기 시작하면 B가 필요한 코드와 데이터를 가져오면서 캐시의 내용과 활용 패턴이 달라집니다.

다시 A로 돌아왔을 때 필요한 데이터가 캐시에 남아 있지 않다면 메모리 계층에서 다시 가져와야 합니다.

즉 Context Switch가 많아지면 단순히 전환 작업에 필요한 시간뿐 아니라 Cache Locality 저하에 따른 간접 비용도 발생할 수 있습니다.

주소 공간이 달라지는 프로세스 간 전환에서는 TLB와 메모리 관리 측면의 비용도 고려해야 합니다. 다만 최신 CPU와 Linux는 ASID/PCID와 같은 메커니즘을 활용할 수 있기 때문에 모든 Context Switch가 항상 전체 TLB를 비우는 식으로 동작한다고 이해하면 안 됩니다.

이 차이를 알고 있어야 실제 성능 분석에서 Context Switch 비용을 과장하지 않을 수 있습니다.


Voluntary와 Involuntary Context Switch의 차이

Linux에서 Context Switch를 분석할 때 중요한 것이 있습니다.

모든 Context Switch가 같은 이유로 발생하지 않는다는 것입니다.

대표적으로 두 가지를 구분해야 합니다.

Voluntary Context Switch

실행 중인 태스크가 스스로 CPU 실행을 이어갈 수 없는 상황에서 발생합니다.

예를 들어 다음과 같은 경우입니다.

Application
    ↓
read()
    ↓
Disk / Network I/O 대기
    ↓
Sleep
    ↓
다른 Task 실행

파일 읽기, 네트워크 응답 대기, Lock 대기 등으로 태스크가 Sleep 상태로 들어가면 다른 태스크가 CPU를 사용할 수 있습니다.

Involuntary Context Switch

현재 태스크가 계속 실행할 수 있지만 Scheduler가 CPU를 다른 태스크에 배분하면서 발생할 수 있습니다.

예를 들어 CPU를 사용하려는 실행 가능한 태스크가 많아 경쟁이 심해진 상황입니다.

따라서 Involuntary Context Switch가 크게 증가하면서 Run Queue까지 길어지고 있다면 CPU 경쟁을 의심할 수 있습니다.

반대로 Voluntary Context Switch가 매우 많다면 I/O 대기나 Lock 경쟁 등 애플리케이션 동작 특성을 함께 살펴봐야 합니다.

Context Switch 숫자 하나만 보는 것보다 어떤 종류의 전환이 증가했는지를 구분하는 것이 훨씬 중요합니다.


실제 서버에서는 어떤 증상이 나타날까?

Context Switch가 과도해졌을 때 가장 먼저 보이는 현상이 반드시 CPU 100%인 것은 아닙니다.

오히려 운영자가 이상하다고 느끼는 경우는 이런 상황입니다.

CPU Usage      65%
Memory         정상
Disk I/O       정상

그런데

API Response   느려짐
TPS            감소
Load Average   상승

CPU 사용률만 보면 아직 35%의 여유가 있는 것처럼 보입니다.

하지만 내부적으로 실행 가능한 스레드가 많고 CPU가 계속 태스크를 전환하고 있다면 실제 애플리케이션이 유용한 작업을 수행하는 비율이 기대보다 낮아질 수 있습니다.

또 다른 특징은 Latency 변동폭 증가입니다.

평균 응답시간은 크게 변하지 않았는데 일부 요청의 응답시간이 갑자기 증가할 수 있습니다.

예를 들어 평상시에는

50ms
55ms
48ms
60ms

정도였던 요청이 부하 증가 후

55ms
90ms
170ms
65ms
350ms

처럼 흔들릴 수 있습니다.

CPU를 언제 배정받을지에 대한 대기 시간이 커지면 Tail Latency가 악화될 가능성이 있기 때문입니다.

웹 서비스에서는 평균 응답시간만 보는 것보다 p95, p99 Latency를 함께 확인해야 하는 이유이기도 합니다.


Linux에서 Context Switch를 어떻게 분석할까?

가장 기본적으로 사용할 수 있는 명령어가 vmstat입니다.

vmstat 1

여기에서 중요한 항목 중 하나가 cs입니다.

r  b   swpd   free   buff  cache   si   so   bi   bo   in    cs us sy id wa
3  0      0  ...     ...    ...     0    0    0    5  800  4200 35 15 50  0

cs는 초당 발생하는 Context Switch를 보여줍니다.

하지만 여기서 흔히 하는 실수가 있습니다.

“cs가 몇 이상이면 문제다”라고 절대적인 숫자를 정하는 것입니다.

이는 올바른 분석 방법이 아닙니다.

CPU 코어 수, 실행 중인 서비스, 트래픽, 프로세스 수, 애플리케이션 구조에 따라 정상적인 Context Switch 수준이 크게 다르기 때문입니다.

따라서 다음과 같이 비교해야 합니다.

정상 트래픽
cs = 8,000/sec

장애 발생
cs = 45,000/sec

즉 절대값보다 정상 상태의 Baseline 대비 얼마나 증가했는지가 중요합니다.

그리고 cs만 보면 안 됩니다.

vmstat의 r도 함께 봐야 합니다.

r은 CPU에서 실행되거나 실행을 기다리는 runnable task의 규모를 판단하는 데 도움을 줍니다.

CPU가 8개인데 지속적으로 실행 가능한 태스크가 크게 누적되고 있다면 CPU 경쟁이 발생하고 있을 가능성이 있습니다.


더 자세히 보려면 pidstat도 유용합니다.

pidstat -w 1

여기에서는 프로세스별 Context Switch를 확인할 수 있습니다.

대표적인 항목은 다음과 같습니다.

cswch/s
nvcswch/s

cswch/s는 voluntary context switches를, nvcswch/s는 non-voluntary context switches를 나타냅니다.

이 명령어가 중요한 이유는 시스템 전체 Context Switch가 증가했다는 사실에서 한 단계 더 들어가 어떤 프로세스가 전환을 많이 발생시키는지 좁힐 수 있기 때문입니다.

필요하다면 스레드 단위로도 확인합니다.

pidstat -wt -p <PID> 1

멀티스레드 애플리케이션에서는 프로세스 전체 수치만 보면 실제 문제를 일으키는 특정 스레드의 동작이 가려질 수 있기 때문입니다.


CPU별 실행 상태를 확인할 때는 mpstat도 사용할 수 있습니다.

mpstat -P ALL 1

여기서는 CPU별 사용률을 비교할 수 있습니다.

예를 들어 16코어 서버인데 특정 CPU 몇 개에만 부하가 집중되어 있다면 전체 평균 CPU 사용률은 낮게 보일 수 있습니다.

CPU 0   95%
CPU 1   92%
CPU 2   88%

CPU 3~15
20~30%

이 경우 단순히 전체 CPU 사용률이 40%라고 해서 CPU에 충분한 여유가 있다고 판단하면 안 됩니다.

IRQ affinity, 애플리케이션 CPU affinity, NUMA 구조 등으로 특정 CPU에 작업이 집중되는 상황도 확인해야 합니다.


Context Switch가 많을 때 어떻게 해결해야 할까?

가장 먼저 해서는 안 되는 것이 있습니다.

Context Switch가 많다는 이유만으로 CPU를 바로 증설하는 것입니다.

먼저 증가 원인을 찾아야 합니다.

예를 들어 애플리케이션 Worker가 지나치게 많다고 가정해보겠습니다.

CPU Core : 8

Application Worker
100 Threads

스레드가 많다고 항상 처리량이 증가하는 것은 아닙니다.

CPU-bound 작업에서는 실행 가능한 스레드 수가 CPU 처리 능력을 크게 초과하면 스케줄링 경쟁과 Context Switch만 늘어날 수 있습니다.

이 경우 Worker 수를 적절하게 조정하는 것이 CPU 증설보다 효과적일 수 있습니다.

반대로 대부분의 스레드가 네트워크나 디스크 I/O를 기다리는 I/O-bound 서비스라면 CPU 코어 수보다 많은 스레드가 반드시 잘못된 설정이라고 할 수 없습니다.

따라서 다음과 같이 접근해야 합니다.

Context Switch 증가
        ↓
Voluntary / Involuntary 구분
        ↓
Run Queue 확인
        ↓
CPU별 사용률 확인
        ↓
문제 PID / Thread 추적
        ↓
I/O Wait / Lock / IRQ 확인
        ↓
Worker·Thread·CPU 구조 조정

Lock 경쟁도 중요한 원인입니다.

여러 스레드가 하나의 공유 자원을 두고 경쟁하면 다음과 같은 패턴이 반복될 수 있습니다.

Thread A → Lock 획득
Thread B → Lock 대기
Thread C → Lock 대기

Thread A → Unlock

Thread B → Wake-up

이런 구조에서는 스레드 수를 늘려도 처리량이 증가하지 않고 오히려 스케줄링과 동기화 비용만 증가할 수 있습니다.

이때는 단순히 서버 사양을 높이는 것보다 애플리케이션의 Lock 범위를 줄이거나 공유 자원 접근 구조를 개선하는 것이 근본적인 해결책이 될 수 있습니다.


Context Switch는 원인이 아니라 단서일 수 있다

운영 서버를 분석할 때 가장 중요한 부분입니다.

Context Switch가 높다고 해서 그것만으로 장애 원인이 확정되는 것은 아닙니다.

예를 들어 다음과 같은 상황이 있을 수 있습니다.

트래픽 증가
   ↓
요청 처리 Thread 증가
   ↓
Runnable Task 증가
   ↓
Context Switch 증가

이 경우 Context Switch 증가는 트래픽 증가로 인해 나타난 결과일 수 있습니다.

또 다른 경우에는

Lock 경쟁
   ↓
Sleep / Wake-up 반복
   ↓
Context Switch 증가
   ↓
처리량 감소

처럼 애플리케이션 내부 문제가 Context Switch 증가로 드러날 수도 있습니다.

따라서 운영자는 다음 지표를 함께 봐야 합니다.

지표확인 목적
Context Switch태스크 전환 빈도
Run QueueCPU 실행 대기 규모
CPU Usage실제 CPU 사용 상태
System CPUKernel 작업 증가 여부
I/O WaitI/O 병목 가능성
InterruptIRQ 처리 증가 여부
p95/p99 Latency실제 서비스 지연
Worker/Thread 수과도한 동시 실행 여부

이 지표들이 같은 시점에 어떻게 변했는지를 비교해야 원인과 결과를 구분할 수 있습니다.


정리

Context Switch는 Linux 서버에서 없어서는 안 되는 정상적인 동작입니다.

멀티태스킹 환경에서는 수많은 프로세스와 스레드가 CPU를 공유해야 하기 때문에 Context Switch 자체를 줄이는 것이 목표가 되어서는 안 됩니다.

문제는 불필요하거나 과도한 Context Switch가 발생하고 있는 경우입니다.

CPU가 실제 요청을 처리하는 것보다 태스크를 교체하고 스케줄링하는 데 많은 비용을 사용하기 시작하면 처리량이 감소하고 응답시간의 변동폭이 커질 수 있습니다.

특히 서버 성능을 분석할 때는 다음과 같은 단순한 판단을 피해야 합니다.

CPU 60%
→ CPU 여유 있음
→ CPU 문제 아님

실제로는 CPU별 부하 불균형, 긴 Run Queue, 과도한 Runnable Thread, Lock 경쟁, 높은 Context Switch가 숨어 있을 수 있습니다.

따라서 Context Switch 문제는 숫자 하나로 판단하는 것이 아니라

vmstat → pidstat → mpstat → 프로세스/스레드 → 애플리케이션 구조

순으로 범위를 좁혀가는 것이 좋습니다.

결국 중요한 질문은 **“Context Switch가 몇 번 발생했는가?”가 아니라 “CPU가 왜 이렇게 자주 실행 대상을 바꾸고 있는가?”**입니다.

이 질문까지 들어가야 CPU 사용률이라는 표면적인 지표를 넘어 실제 Linux 서버 내부에서 발생하는 성능 병목을 찾아낼 수 있습니다.

instone
함께 보면 좋은 글

댓글 0

첫 댓글을 남겨보세요.

error: Content is protected !!

광고 차단 알림

광고 클릭 제한을 초과하여 광고가 차단되었습니다.

단시간에 반복적인 광고 클릭은 시스템에 의해 감지되며, IP가 수집되어 사이트 관리자가 확인 가능합니다.