Overview
CPU는 정말 동시에 여러 작업을 처리할까?
우리는 매일 여러 컨테이너, 프로세스, 스레드를 동시에 실행하면서 “멀티태스킹”을 당연하게 여기지만, 실제로 CPU는 매 순간 오직 하나의 작업만 처리한다.
이렇게 빠르게 작업을 전환해 멀티태스킹처럼 보이게 만드는 핵심 기술이 바로 Context Switching이다.
하지만 이 context switch는 무료가 아니다.
컨테이너가 많아질수록, 작업 전환이 잦아질수록, CPU는 본업인 연산보다 문맥을 저장하고 복구하는 데 더 많은 리소스를 쓰게 된다.
이 글에서는 Context Switching이 무엇인지, 왜 이것이 컨테이너 환경에서 성능 저하와 리소스 낭비로 이어지는지, 실무에서 어떤 방식으로 이를 줄일 수 있는지 살펴본다.

📅 관련 글
2023.01.13 - [CS 지식] - [CS 지식1.] 웹 브라우저의 동작원리
2023.02.23 - [CS 지식] - [CS 지식2.] DNS의 동작원리(Domain Name System)
2023.03.06 - [CS 지식] - [CS 지식3.] HTTP / HTTPS 란?
2023.03.07 - [CS 지식] - [CS 지식4.] OSI 7계층 & TCP/IP 4계층이란?
2023.03.17 - [CS 지식] - [CS 지식5.] 가상화란?
2023.05.24 - [CS 지식] - [CS 지식6.] HTTP 메서드(Method)란? / HTTP Status Code
2023.12.05 - [CS 지식] - [CS 지식7.] Kubernetes 구성요소와 Pod 생성 방식이란?
2023.12.19 - [CS 지식] - [CS 지식8.] 프로세스(Process)와 스레드(Thread)란?
2023.12.30 - [CS 지식] - [CS 지식9.] 클라우드 컴퓨팅이란?(Public & Private Cloud / IaaS SaaS PaaS / Multitenancy)
2024.01.05 - [CS 지식] - [CS 지식10.] 웹1.0(Web1.0) vs 웹2.0(Web2.0) vs 웹3.0(Web3.0)
2024.02.02 - [CS 지식] - [CS 지식11.] NAT(Network Address Translation)란?
2024.05.22 - [CS 지식] - [CS 지식13.] 동기 및 비동기 처리란?
2024.05.23 - [CS 지식] - [CS 지식14.] 3tier 아키텍처란?
2024.08.28 - [CS 지식] - [CS 지식15.] SSR vs CSR vs ISR vs SSG
2024.11.09 - [CS 지식] - [CS 지식16.] stdin(표준입력) vs stdout(표준출력) vs stderr(표준에러)
2024.11.11 - [CS 지식] - [CS 지식17.] IPsec vs SSL/TLS
2024.11.22 - [CS 지식] - [CS 지식18.] Quantum Computing(양자 컴퓨팅)
2025.04.01 - [CS 지식] - [CS 지식19.] C/C++ 개발자도 다시 보는 메모리 구조
2025.04.02 - [CS 지식] - [CS 지식20.] OS 캐시와 디스크 I/O: MySQL, Redis 퍼포먼스 분석
2025.04.02 - [CS 지식] - [CS 지식21.] top에 나오는 load average, 진짜 뜻은?
2025.04.09 - [CS 지식] - [CS 지식22.] TLS Handshake와 인증서 구조
Context Switching이란?
Context Switching(문맥 전환)은 CPU가 현재 작업 중인 프로세스 또는 스레드의 상태(context)를 저장하고, 다른 작업의 상태로 전환하는 동작이다.
전환 시 저장되는 context 정보는 다음과 같다.
- CPU 레지스터 상태
- 프로그램 카운터 (PC)
- 스택 포인터
- 메모리 매핑 정보
이 과정을 OS가 관리하며, CPU는 작업 A → 작업 B로 매우 빠르게 전환해 마치 동시에 여러 작업을 하는 것처럼 보이게 된다.
왜 컨테이너 환경에서 더 민감할까?
1. 컨테이너는 경량이지만 많은 수의 프로세스를 생성
- 수십~수백 개의 컨테이너가 동시에 실행되며, 각 컨테이너는 자신의 프로세스와 스레드를 갖는다.
- 이들 간에 CPU를 공유하다 보면 문맥 전환 빈도가 급증한다.
2. 다중 컨테이너 = 더 많은 task 전환
- CPU core 수는 제한되어 있고, 컨테이너 수가 많을수록 스케줄러가 전환을 더 자주 수행한다.
- 특히 CPU intensive task들이 겹치면, context switching overhead가 급격히 증가한다.
3. cgroups, namespace 등도 context 저장/복구 비용을 유발
- 격리된 환경은 좋지만, 그만큼 추가적인 상태 관리 비용이 따라온다.
성능 분석: 어떻게 확인할 수 있을까?
1. `pidstat` 또는 `vmstat` 로 context switch 모니터링
pidstat -w 1
Output
Linux 6.8.0-55-generic (gitlab) 04/10/25 _x86_64_ (2 CPU)
03:35:19 UID PID cswch/s nvcswch/s Command
03:35:20 0 11 1.98 0.00 kworker/u4:0-ext4-rsv-conversion
03:35:20 0 17 29.70 0.00 rcu_preempt
03:35:20 0 18 0.99 0.00 migration/0
03:35:20 0 23 0.99 0.00 migration/1
03:35:20 0 37 1.98 0.00 kcompactd0
03:35:20 0 52 0.99 0.00 kworker/1:1H-kblockd
03:35:20 0 2368165 0.99 0.00 multipathd
03:35:20 0 3186534 1.98 0.00 svlogd
03:35:20 0 3186548 0.99 0.00 svlogd
03:35:20 999 3188704 2.97 0.00 nginx
03:35:20 999 3188705 3.96 0.00 nginx
03:35:20 994 3188730 1.98 0.00 postgres
03:35:20 994 3188734 4.95 0.00 postgres
03:35:20 994 3188735 3.96 0.00 postgres
03:35:20 994 3188736 5.94 0.99 postgres
03:35:20 992 3188741 0.99 0.00 prometheus
03:35:20 995 3188781 43.56 0.99 redis-server
03:35:20 0 3194098 2.97 0.00 kworker/u5:3-events_unbound
03:35:20 0 3199185 1.98 0.00 kworker/u6:2-flush-253:0
03:35:20 0 3201849 1.98 0.00 kworker/0:1-events
03:35:20 994 3202264 0.99 0.00 postgres
03:35:20 994 3202268 5.94 0.00 postgres
03:35:20 0 3202490 0.99 0.00 kworker/u6:0-events_power_efficient
03:35:20 0 3202625 0.99 0.00 kworker/1:2-mm_percpu_wq
03:35:20 994 3202646 0.99 0.00 postgres
03:35:20 1000 3204393 0.99 0.00 pidstat
2. `perf, top, htop, dstat` 활용
- `perf record/report` 로 어떤 함수 또는 커널 경로에서 전환이 많이 발생하는지 분석 가능
sudo perf record -g
- `-g` 옵션은 호출 스택(call graph)을 같이 기록
- 기본적으로 현재 시스템에서 실행 중인 전체 workload를 수집
sudo perf report
- `perf.data` 파일을 읽어 분석 보고서 출력
- 어떤 함수나 커널 경로에서 CPU 시간을 가장 많이 썼는지 보여줘
예시: 특정 프로세스(pid)만 분석하고 싶을 때
sudo perf record -p <PID> -g
sudo perf report
- 이 방법은 컨테이너 내부 또는 호스트에서 특정 프로세스에 대해 context switch나 CPU hotspot을 추적할 때 유용하다.
3. 컨테이너별 CPU usage 및 context switching 추적
- `cadvisor, Prometheus + node_exporter` 에서 context_switches 지표 수집 가능
리소스 낭비와 성능 저하 사례
- CPU 사용률이 높은데 실제 서비스 처리량은 낮은 경우
- 컨테이너 수가 많아질수록 응답 지연/스레드 전환이 심해지는 현상
- 다수의 idle container가 쓸데없이 context switch 비용만 발생시키는 경우
실무에서의 해결 전략
| 전략 | 설명 |
| CPU affinity 설정 | 특정 core에 특정 컨테이너를 고정시켜 switch를 줄임 |
| 컨테이너 수 최적화 | 불필요한 sidecar, idle container 제거 |
| CFS quota 조정 | 컨테이너의 CPU share 제어해 preempt 줄이기 |
| `nice`, `cpuset`, `taskset` 활용 | 우선순위 및 CPU 바인딩 제어 |
| 경량화된 스레드 모델 선택 | Node.js보다는 Go, Nginx처럼 스레드 효율성 높은 환경 고려 |
1. CPU affinity 설정
하나의 프로세스나 컨테이너가 항상 같은 CPU 코어에서 실행되도록 고정하면, context switch 발생 시 CPU 캐시를 재활용할 수 있어 성능 향상에 유리함. 이를 CPU affinity(코어 고정)라고 한다.
`taskset` 명령어로 프로세스에 코어 바인딩
taskset -c 0 ./my_app
- Kubernetes에서는 cpuset cgroup을 활용하거나, cpuManagerPolicy: static 설정으로 Pod 단위 고정
효과
- 캐시 일관성 유지 (cache locality)
- core 간 전환 최소화 → context switch 횟수 줄어듬
2. 컨테이너 수 최적화
컨테이너는 가볍지만, 많아지면 스케줄러가 너무 자주 전환하게 되고 리소스 간 경합이 생김. 특히,
- 필요 없는 `sidecar`,
- 역할이 겹치는 `debug container`,
- 사용률이 낮은 `idle container`
는 줄이는 것이 성능 유지에 중요함.
예시 전략
- Fluentd/log sidecar → DaemonSet으로 대체
- metrics sidecar → Host exporter로 통합
- 템플릿 렌더링 전용 container → initContainer로 이동
효과
- 전체 시스템 내 프로세스 수 감소
- CPU 스케줄러가 할당할 task 수 줄어 context switch 빈도도 감소
3. CFS quota 조정 (CPU share 제어)
리눅스의 CFS(Completely Fair Scheduler)는 CPU 사용 시간(quota)을 기준으로 스레드를 스케줄링함.
컨테이너는 `cpu.cfs_quota_us` 설정을 통해 정해진 시간만 CPU를 사용하게 제어 가능.
예시 (Docker/K8s):
resources:
requests:
cpu: "0.5"
limits:
cpu: "1"
- request는 soft limit (CPU가 여유 있을 때까지는 더 사용 가능)
- limit은 hard limit (그 이상 사용 불가)
효과
- 과도한 CPU 사용으로 인한 강제 전환 (non-voluntary context switch) 예방
- 컨테이너 간 균형 잡힌 자원 배분
4. nice, cpuset, taskset 활용 (우선순위/코어 제한)
설명
- `nice`: 프로세스의 우선순위 조정 (-20~19)
- `cpuset`: 특정 컨테이너/프로세스를 특정 코어에 고정
- `taskset`: 커맨드 단위 코어 지정 실행
예시
nice -n 10 ./low_priority_task
taskset -c 2 ./compute_heavy_job
효과
- 비중요한 작업을 낮은 우선순위로 설정 → 중요 프로세스 CPU 보장
- 동일한 CPU 코어 내에서만 컨텍스트 전환 유도 → 캐시 유지 가능
5. 경량화된 스레드 모델 선택
언어나 런타임의 스레드 모델에 따라 context switch 비용 차이가 발생함.
- Node.js: 단일 스레드 기반 → CPU-bound에는 비효율
- Go: goroutine은 user-level 경량 스레드로, 커널 context switch 없이 동시성 처리 가능
- Nginx: 이벤트 기반 처리를 통한 고효율 I/O
적용 예시
- 고빈도 API 서버: Node.js → Go or Nginx 기반 마이그레이션
- 이미지 처리 서버: Python → Rust or C++ 기반 멀티프로세스 구조
효과
- 커널 수준 스레드 전환 감소
- CPU 부하 감소 + 캐시 재사용 향상
Voluntary vs Non-voluntary Context Switch 설명
Voluntary Context Switch
- 프로세스나 스레드가 스스로 CPU를 양보하거나, I/O 요청 등으로 대기 상태에 들어갈 때 발생
- 보통 애플리케이션 설계에 따라 예측 가능한 전환이며, 시스템에 부담이 적음
- 예: `sleep(), select(), read()` 같은 시스템 콜 사용 시
Non-voluntary Context Switch
- 커널 스케줄러가 강제로 현재 실행 중인 프로세스를 중단시키고 다른 태스크로 전환
- 이유: CPU 할당 시간이 끝났거나, 우선순위가 높은 태스크가 등장했기 때문
- 이 전환은 CPU 캐시 손실 및 메모리 접근 비용까지 발생시키므로 성능 저하의 주요 원인
팁
- nvcswch 값이 특정 프로세스에서 유난히 높다면, 해당 작업이 CPU 자원을 독점하거나 경합이 심한 상태일 수 있다.
- CPU-intensive 작업은 적절히 쪼개서 비동기 처리 혹은 작업 큐로 분산시키는 게 효과적이다.
멀티코어 환경에서의 CPU 캐시 무효화(CPU cache invalidation) 이슈
- CPU는 core마다 L1/L2 캐시를 갖고 있음
- 컨텍스트가 다른 코어로 전환되면 캐시가 무효화(invalidate)되고, 새 코어는 다시 데이터를 메모리에서 가져와야 함
- 이 현상은 특히 멀티코어 컨테이너 환경에서 성능 손실의 주요 원인
해결 방법
- taskset / cpuset: 특정 프로세스를 특정 코어에 고정
- Docker, Kubernetes에서 CPU pinning 설정: 컨테이너가 항상 동일한 core를 쓰게 함 → 캐시 일관성 유지
- NUMA 아키텍처 서버에서는 노드(local memory)에 따른 메모리 접근 위치도 고려해야 더 큰 최적화 가능
Kubernetes 환경에서 context switch 줄이기
- cpuManagerPolicy: static 설정
- Pod 단위 CPU 고정 할당으로 컨텍스트 전환 최소화
- `kubelet config` 수정 필요
cpuManagerPolicy: static
- 이 정책은 Guaranteed QoS Pod에 한해 물리 CPU 코어 고정을 허용함
- 이를 통해 CPU switching 감소 + 캐시 재사용률 향상
- QoS Class가 Guaranteed인 Pod는 고정 CPU 할당이 용이
resources:
requests:
cpu: "1"
limits:
cpu: "1"
- requests와 limits가 같아야 Guaranteed 클래스 적용됨
Thread/Coroutine 수요에 따른 언어별 차이
- Node.js는 단일 스레드 + 이벤트 루프 방식이지만, CPU-bound 작업에는 오히려 비효율적
- Go, Rust는 경량 쓰레드(`goroutine`), `async runtime` 이 잘 발달되어 context switch 부담이 낮음
- Nginx는 비동기 이벤트 기반 처리로 많은 요청을 소수의 프로세스/스레드로 커버
| 언어/환경 | 스레드 모델 | context switching 영향도 | 특징 |
| Node.js | 단일 스레드, 이벤트 루프 | 낮음 (I/O 중심에는 강점), CPU-bound에 약함 | 단순하지만 연산 병목 발생 |
| Go | 경량 스레드(goroutine) + runtime scheduler | 낮음 | 병렬성 + 동시성 동시 제공 |
| Rust | async/await 기반, 런타임 최소 | 매우 낮음 | zero-cost abstraction |
| Java, Python (threading) | 커널 스레드 기반 | 높음 | GIL, GC 등이 변수 |
| Nginx, Envoy | 이벤트 기반 논블로킹 I/O | 매우 낮음 | 고성능 네트워크 처리 특화 |
결론
- 웹서버나 API 게이트웨이처럼 I/O 중심 workload는 Node.js나 Nginx 기반으로도 충분
- 반면 데이터 처리, 이미지 변환, 대용량 연산은 Go, Rust, 또는 멀티 프로세스 구조가 효율적
실무에서 마주치는 전형적인 병목 패턴
- 컨테이너 100개 이상으로 구성된 마이크로서비스에서 Redis, Fluentd, Envoy, Sidecar proxy 등이 몰리며 CPU 경합 → nvcswch 급증
- HPA로 pod 수만 늘렸더니, 오히려 context switching overhead 증가로 응답시간이 더 느려지는 역설 발생
- 해결: 불필요한 sidecar 제거, 작업 큐 구조 도입, CPU affinity 설정으로 개선
마무리: "멀티태스킹의 그림자, Context Switch를 이해하자"
CPU는 전지전능하지 않다.
우리가 생각하는 멀티태스킹은 사실 빠른 context switching으로 흉내 내는 것이다.
하지만 컨테이너 환경처럼 프로세스가 넘쳐나는 시대에는, 이 전환 비용이 시스템 성능을 갉아먹는 “숨은 리스크”가 된다.
단순한 성능 문제로 보이던 현상이 사실은 문맥 전환 병목 때문일 수도 있다.
적절한 모니터링과 컨테이너 구성 전략만으로도 context switching을 크게 줄일 수 있다.
진짜 빠른 시스템은, 많은 일을 동시에 하는 게 아니라 불필요한 전환을 줄이는 구조에서 나온다.
이제부터는 CPU usage뿐 아니라, context switching이라는 숨은 비용도 함께 살펴보자.
멀티태스킹은 마법이 아니다.
컨테이너의 시대, 우리는 빠른 처리보다 지속 가능한 시스템 구조를 만들어야 한다.
CPU는 계산에 집중해야지, 자꾸 저장하고 불러오느라 바빠지면 안 된다.
context switching을 줄이는 건 ‘성능 최적화’이자 ‘리소스 절약의 시작’이다.
Reference
https://linux.die.net/man/1/pidstat
https://access.redhat.com/solutions/416983
http://www.brendangregg.com/blog/
https://github.com/google/cadvisor
'CS 지식' 카테고리의 다른 글
| [CS 지식22.] TLS Handshake와 인증서 구조 (0) | 2025.09.10 |
|---|---|
| [CS 지식21.] top에 나오는 load average, 진짜 뜻은? (2) | 2025.08.27 |
| [CS 지식20.] OS 캐시와 디스크 I/O: MySQL, Redis 퍼포먼스 분석 (3) | 2025.08.13 |
| [CS 지식19.] C/C++ 개발자도 다시 보는 메모리 구조 (3) | 2025.07.30 |
| [CS 지식18.] Quantum Computing(양자 컴퓨팅) (2) | 2024.11.22 |