사전 포화
개념

포화

gabury1

포화는 자원 하나가 감당할 수 있는 한계에 얼마나 몰렸는지 알려 주는 값입니다. 한계에 닿은 자원은 들어온 일을 제때 처리하지 못합니다. 서비스가 멀쩡한지 볼 때 맨 먼저 보는 신호 넷, 곧 골든 시그널 가운데 하나입니다.

쉽고 빠른 이해

무슨 일을 하는 값인가 — 자원 하나가 얼마나 찼는지 보여 줍니다. 커넥션 풀에서 커넥션을 기다리는 스레드가 늘기 시작하면 포화가 오르고 있다는 신호입니다.

왜 재나 — 많은 시스템은 자원이 다 차기 전부터 느려집니다. 응답이 늦거나 실패한 것만 봐서는 어느 자원이 원인인지 알기 어렵습니다.

어떻게 재나

  1. 서비스에서 가장 빠듯한 자원을 고릅니다
  2. 그 자원을 얼마나 쓰고 있는지, 또는 그 앞에 일이 얼마나 밀렸는지를 잽니다
  3. 문제가 되기 직전에 알림을 보냅니다

대가 — 자원마다 볼 값이 다릅니다. 같은 포화라는 이름 아래 사용률을 보는 쪽과 밀린 일을 보는 쪽이 갈립니다. 긴 구간의 평균으로 재면 잠깐 찼다 빠지는 포화를 놓칠 수 있습니다.

잘 안 맞을 때 — 디스크 용량처럼 다 차면 바로 오류가 나는 자원에서는 포화를 어떻게 볼지부터 갈립니다.

상세

마트 계산대에 계산원이 한 명 있습니다. 손님이 드문드문 오면 오는 대로 바로 계산을 받습니다. 손님이 계산하는 속도보다 빨리 몰려오면 계산대 앞에 줄이 생깁니다. 손님이 계속 오면 줄은 점점 길어집니다.

서버의 자원도 한 번에 처리할 수 있는 양이 정해져 있습니다. 포화는 자원이 이 한계에 얼마나 몰렸는지를 나타내는 값입니다.

그 정도를 무엇으로 재느냐는 둘로 나뉩니다. 한쪽은 자원이 가진 것 가운데 얼마를 쓰고 있는지, 곧 얼마나 찼는지를 봅니다. 다른 쪽은 한계에 닿아 처리하지 못한 채 밀린 일이 얼마나 되는지를 봅니다. 두 쪽이 누구인지는 「이견」 절이 다룹니다.

예를 들어 CPU(Central Processing Unit, 중앙 처리 장치)가 쉬지 않고 돌고 있습니다. 그런데도 실행 차례를 기다리는 작업이 넷 더 줄 서 있습니다. 앞쪽은 CPU 가 다 찼다는 것을 봅니다. 뒤쪽은 줄 선 작업 넷을 봅니다.

포화를 따지는 자원

포화를 따질 수 있는 자원은 처리할 수 있는 양에 위쪽 한계가 있는 것입니다. 서버를 이루는 물리 부품이 먼저 꼽힙니다. CPU · 메모리 · 디스크 · 네트워크 인터페이스가 그렇습니다.

소프트웨어 안에도 한계가 정해진 자원이 있습니다. 그 가운데 일부는 같은 방식으로 따질 수 있습니다. 대개 애플리케이션 전체가 아니라 그 안의 작은 부품입니다.

뮤텍스 락은 한 번에 한 스레드만 쥐는 잠금입니다. 다른 스레드는 락이 풀리기를 기다리며 줄을 섭니다. 스레드 풀은 스레드 수가 정해져 있습니다. 남는 요청은 처리를 기다립니다. 이런 자원에서는 기다리는 스레드나 요청의 수를 포화로 볼 수 있습니다.

한계를 넘은 일의 두 갈래

자원에 여유가 있으면 들어온 일은 바로 처리됩니다. 한계에 닿은 자원은 일을 더 받지 못합니다. 그 일은 흔히 대기열에 들어가 차례를 기다립니다. 대기열 대신 거절되어 오류로 돌아가기도 합니다.

flowchart TD
    A[들어온 일] --> B{자원에 여유가 있나}
    B -->|있다| C["바로 처리 · 사용률에 잡힘"]
    B -->|없다| D["대기열에서 차례를 기다림 · 줄 길이와 기다린 시간에 잡힘"]
    B -->|없다| E["거절되어 오류로 돌아감 · 오류 수에 잡힘"]

갈래마다 잡히는 값이 다릅니다. 사용률로 재는 쪽은 바로 처리되는 일이 자원을 얼마나 채웠는지를 봅니다. 밀린 일로 재는 쪽은 기다리는 갈래를 봅니다. 그쪽에서는 줄 선 일의 수나 그 줄에서 기다린 시간으로 포화를 잴 수 있습니다. 거절된 일은 두 쪽 모두 오류로 따로 셉니다.

가장 빠듯한 자원

서비스 하나에는 자원이 여럿입니다. 포화를 볼 때는 그중 가장 빠듯한 자원에 무게를 둡니다. 메모리가 먼저 모자라는 시스템이면 메모리를 봅니다. 디스크 입출력이 먼저 막히는 시스템이면 입출력을 봅니다.

자원 하나가 한계에 닿아 전체 흐름을 붙잡고 있으면 그 자원을 병목이라 부릅니다. 포화가 오른 자원은 병목 후보입니다.

배경

서비스가 느려지거나 실패하면 요청 쪽에서 먼저 보입니다. 응답이 늦어지는 지연, 실패한 응답의 비율인 오류율, 들어오는 요청의 양인 트래픽이 그런 값입니다. 이 셋은 요청이 겪은 결과를 잽니다. 요청을 붙잡은 것이 어느 자원인지는 이 셋만으로 드러나지 않습니다. 데이터베이스 같은 하위 시스템의 포화는 흔히 그 하위 시스템에서 직접 재야 합니다.

자원이 다 찬 것을 확인한 뒤에 움직이면 늦을 수 있습니다. 많은 시스템은 사용률이 100퍼센트에 닿기 전부터 성능이 떨어집니다. 그래서 자원마다 한계와의 거리를 따로 재는 값이 필요했습니다. 사용률에 목표선을 두는 것이 꼭 필요한 까닭도 같습니다.

두 곳이 이 값을 포화라는 이름의 감시 신호로 세웠습니다. 서비스를 감시할 때 맨 먼저 볼 신호 넷을 묶은 골든 시그널에서 넷째가 포화입니다. 성능 병목을 빨리 찾는 절차인 USE(Utilization·Saturation·Errors, 사용률·포화·오류) 방법은 자원마다 세 가지를 묻습니다. 그 가운데 S 가 포화입니다.

예시

포화는 제품마다 다른 이름의 값으로 드러납니다. 아래 넷은 커널 · 명령행 도구 · 애플리케이션 라이브러리 · 컨테이너 플랫폼에서 하나씩 고른 것입니다.

Linux 커널의 PSI

여러 작업이 같은 자원을 두고 다투는 것을 경합이라 합니다. Linux 커널은 CPU · 메모리 · 입출력 세 자원의 경합을 PSI(Pressure Stall Information, 압력 정지 정보)로 내보냅니다. 작업이 자원을 기다리며 멈춘(stall) 시간을 잽니다.

값은 /proc/pressure/ 아래 cpu · memory · io 파일에 있습니다. 형식은 이렇습니다.

some avg10=0.00 avg60=0.00 avg300=0.00 total=0
full avg10=0.00 avg60=0.00 avg300=0.00 total=0

두 줄은 멈춘 작업이 얼마나 많은지로 나뉩니다. 칸은 재는 창의 길이로 나뉩니다.

칸 뜻
some 줄 적어도 일부 작업이 그 자원을 기다리며 멈춰 있던 시간의 비율
full 줄 쉬지 않는 작업 전부가 동시에 멈춰 있던 시간의 비율. 이 상태에서는 CPU 시간이 헛되이 흐릅니다
avg10 · avg60 · avg300 10초 · 60초 · 300초 창에서 잰 비율(퍼센트)
total 멈춰 있던 시간의 누계(마이크로초). 평균에 반드시 흔적을 남기지는 않는 짧은 지연 급등을 잡는 데 씁니다. 직접 정한 구간으로 평균을 내는 데도 씁니다

full 상태에 오래 머무는 워크로드는 스래싱 중이라고 봅니다.

CPU 의 full 줄은 시스템 전체 수준에서는 뜻이 정해져 있지 않습니다. 그래도 이 줄은 커널 5.13 부터 출력에 들어 있었습니다. 하위 호환을 위해 값을 0 으로 둡니다. 그래서 이 0 은 CPU 앞에서 멈춘 작업이 없었다는 뜻이 아닙니다.

vmstat 의 r 칸

vmstat 은 프로세스 · 메모리 · 스왑 상태를 주기마다 한 줄씩 보여 주는 명령입니다. 출력의 Procs 묶음 첫 칸 r 은 실행할 수 있는 프로세스의 수입니다. 지금 도는 것과 실행 차례를 기다리는 것을 함께 셉니다. 그래서 r 에서 CPU 개수를 넘는 몫은 CPU 앞에 줄을 선 프로세스로 읽습니다.

같은 출력의 Swap 묶음에는 si 와 so 가 있습니다. si 는 디스크에서 메모리로 스왑해 들인 양입니다. so 는 메모리에서 디스크로 스왑해 내보낸 양입니다. 둘 다 초당 값입니다.

메모리 용량이 포화하면 시스템이 감당할 수 있는 것보다 많은 메모리를 요구받습니다. 그러면 페이징과 스와핑이 일어납니다. 이 두 값이 그때 움직입니다.

HikariCP 커넥션 풀

커넥션 풀 라이브러리 HikariCP 는 2.2.0 부터 지표 라이브러리 Dropwizard Metrics 를 지원합니다. 지표 저장소(MetricRegistry)를 설정해 두면 풀 상태를 지표로 모읍니다. 그중 넷이 포화와 맞닿습니다.

지표 뜻
<pool name>.pool.PendingConnections 풀에서 커넥션이 나오기를 기다리는 스레드 수
<pool name>.pool.Wait getConnection() 을 부른 스레드가 커넥션을 받기까지, 또는 타임아웃 예외가 나기까지 기다린 시간
<pool name>.pool.ActiveConnections 지금 쓰이고 있는 커넥션 수
<pool name>.pool.TotalConnections 풀 안의 커넥션 총수

PendingConnections 는 풀 앞에 선 줄의 길이입니다. Wait 는 그 줄에서 보낸 시간입니다. 밀린 일로 포화를 재는 두 방식이 지표 둘로 나뉘어 있습니다.

ActiveConnections 를 TotalConnections 에 견주면 풀이 얼마나 찼는지가 나옵니다. 사용률로 재는 쪽이 보는 값입니다. PendingConnections · ActiveConnections · TotalConnections 세 값은 지표를 읽을 때 새로 고칩니다. 새로 고치는 단위는 1초입니다.

Kubernetes 컨테이너의 CPU 한도

여러 사용자가 한 시스템을 나눠 쓰는 클라우드 환경에서는 한 사용자가 쓸 수 있는 양에 한도를 걸 수 있습니다. 이 한도도 물리 자원처럼 포화를 따질 수 있습니다.

Kubernetes 에서는 컨테이너에 CPU 한도(cpu limit)를 걸 수 있습니다. Linux 노드에서는 Linux 커널이 cgroup(control group, 컨트롤 그룹)으로 이 한도를 지킵니다. 컨테이너가 한도에 다가가면 커널이 그 컨테이너의 CPU 사용을 조절(throttling)합니다.

커널은 스케줄링 주기마다 한도를 넘었는지 봅니다. 넘었으면 그 cgroup 이 다시 돌기 전에 기다리게 합니다. 한도에 막힌 일은 거절되지 않고 기다립니다.

메모리 한도는 다르게 지켜집니다. 컨테이너가 메모리 한도를 넘으면 커널이 OOM(Out Of Memory, 메모리 부족) 킬로 그 컨테이너를 끝낼 수 있습니다. 커널은 메모리 압력을 감지했을 때만 끝냅니다. 그래서 한도를 넘은 컨테이너가 바로 죽지 않을 수도 있습니다.

flowchart TD
    A[컨테이너가 한도에 닿음] --> B{어느 한도인가}
    B -->|CPU 한도| C["커널이 조절 · 다시 돌기 전에 기다림"]
    B -->|메모리 한도| D{커널이 메모리 압력을 감지했나}
    D -->|예| E["OOM 킬로 끝낼 수 있음"]
    D -->|아니오| F["끝내지 않음 · 한도를 넘어 쓸 수 있음"]

CPU 한도는 상세의 기다리는 갈래로 갑니다. 메모리 한도는 끝내는 쪽으로 갈 수 있습니다.

cgroup v2 에서 CPU 한도는 cpu.max 파일에 적힙니다. 꼴은 $MAX $PERIOD 두 값입니다. $PERIOD 라는 시간이 한 번 지날 때마다 그 cgroup 이 CPU 시간을 $MAX 만큼까지 쓸 수 있다는 뜻입니다. $MAX 와 $PERIOD 에 적는 수가 어느 단위인지는 자료에 없습니다(미확인).

기본값은 max 100000 입니다. $MAX 자리의 max 는 한도가 없다는 뜻입니다. 그래서 한도를 따로 걸지 않은 cgroup 은 CPU 한도 없이 돕니다.

같은 cgroup 의 cpu.pressure 파일은 위 PSI 와 같은 형식으로 그 cgroup 의 CPU 압력을 보여 줍니다.

이견

포화의 정의는 둘로 나뉩니다. 한쪽은 SRE(Site Reliability Engineering, 사이트 신뢰성 엔지니어링) 책입니다. 다른 쪽은 USE 방법입니다. 갈림의 축은 사용률과 포화를 한 값으로 보느냐, 둘로 가르느냐입니다.

SRE 책의 정의

Google 의 책 『Site Reliability Engineering』 6장 「Monitoring Distributed Systems」(Rob Ewaschuk 씀)는 포화를 골든 시그널의 넷째로 둡니다. 정의는 서비스가 얼마나 「찼는지」입니다. 시스템이 가진 것 가운데 얼마를 쓰고 있는지를 잽니다. 그중 가장 빠듯한 자원에 무게를 둡니다.

이 책은 대부분의 서비스가 CPU 사용률이나 네트워크 대역폭처럼 위쪽 한계가 알려진 간접 신호를 써야 한다고 적습니다. 이 정의에서는 사용률이 포화를 재는 한 방법입니다. 이 책은 사용률에 목표선을 두는 것이 꼭 필요하다고도 적습니다.

USE 방법의 정의

Brendan Gregg 는 2012년 글 「Thinking Methodically about Performance」에서 USE 방법을 소개했습니다. 이 글은 사용률과 포화를 가릅니다.

사용률은 정해진 시간 동안 자원이 일하느라 바빴던 시간의 비율입니다. 바쁜 동안에도 자원은 일을 더 받을 수도 있습니다. 더 받지 못하는 정도가 포화입니다. 그 남는 일은 흔히 대기열에서 기다립니다.

적는 방식도 다릅니다. 사용률은 「CPU 하나가 90퍼센트로 돈다」처럼 적습니다. 포화는 「CPU 실행 대기열의 평균 길이가 4」처럼 적습니다. Gregg 는 포화가 0 만 아니면 문제일 수 있다고 봅니다. 포화는 대기열의 길이로 잴 수도, 대기열에서 기다린 시간으로 잴 수도 있다고 적습니다.

메모리처럼 용량으로 재는 자원에서는 Gregg 도 사용률을 쓰고 있는 용량의 비율로 정의합니다. 이 사용률이 100퍼센트에 닿으면 더는 일을 못 받습니다. 그때 일이 대기열에 쌓이면 포화입니다. 거절되면 오류입니다.

SRE 책 USE 방법
포화의 정의 서비스가 얼마나 찼나 자원이 처리 못 하고 밀린 일의 정도
사용률과의 관계 사용률을 포화의 간접 신호로 씀 사용률과 포화를 따로 잼
대표 값 CPU 사용률 · 네트워크 대역폭 실행 대기열 길이 · 대기열에서 기다린 시간
문턱 사용률 목표선을 둔다 0 이 아니면 문제일 수 있다

디스크 용량을 두고

갈림은 디스크 용량에서 다시 보입니다. SRE 책은 「데이터베이스가 네 시간 뒤 하드 디스크를 다 채울 것 같다」를 포화를 미리 내다보는 예로 듭니다.

Gregg 는 USE 방법을 Linux 에 맞춘 점검표도 냈습니다. 자원마다 사용률 · 포화 · 오류를 어느 명령의 어느 값으로 보는지 표로 적은 문서입니다. ENOSPC(No space left on device, 장치에 남은 공간 없음)는 다 찬 장치에 쓰려 할 때 나는 오류입니다. 이 표에서 저장장치 용량 줄의 포화 칸에는 「이게 말이 되는지 확실하지 않다. 다 차면 ENOSPC 다」라고 적혀 있습니다.

같은 표의 파일 서술자 줄에도 같은 물음이 있습니다. 포화 칸에 「이게 말이 되나? 메모리 할당 말고는 줄을 서거나 막히는 일이 없는 것 같다」라고 적혀 있습니다.

같은 현상의 다른 이름

PSI 문서는 포화라는 낱말을 쓰지 않습니다. 같은 현상을 압력(pressure) · 정지(stall)라고 부릅니다. 자원을 두고 작업이 다투는 것은 경합한다(contended)고 적습니다.

운영

자원별로 보는 포화 지표

Linux 서버에서 자원마다 포화를 볼 값은 이렇습니다. 볼 값은 명령 이름과 그 출력의 칸 이름입니다.

자원 볼 값 무엇을 세나
CPU vmstat 1 의 r 이 CPU 개수보다 큼 · sar -q 의 runq-sz 가 CPU 개수보다 큼 실행 대기열의 길이
메모리 용량 vmstat 1 의 si · so · sar -B 의 pgscank + pgscand · dmesg | grep killed 스와핑 · 페이지 스캔 · OOM 킬러 기록
저장장치 입출력 iostat -xnz 1 의 avgqu-sz 가 1 보다 큼, 또는 await 가 높음 대기열의 길이 · 기다린 시간
네트워크 인터페이스 ifconfig 의 overruns · dropped 인터페이스나 운영체제가 남긴 넘침 · 버림 사건

메모리 칸의 페이지 스캔은 메모리가 모자랄 때 도는 페이지 스캐너의 활동입니다.

떨어진 패킷(dropped)은 포화 신호로도, 오류 신호로도 셉니다. 두 원인 모두로 생길 수 있기 때문입니다.

위 표는 uptime 의 load average 를 CPU 칸에 넣지 않습니다. Linux 의 load average 는 깨울 수 없는 상태로 기다리는 작업까지 셉니다. 그 기다림은 대개 입출력입니다.

사용률 문턱

사용률 100퍼센트는 대개 병목이라는 신호입니다. 확인은 포화와 그 영향으로 합니다. 대략 60~70퍼센트를 넘는 높은 사용률도 문제가 되기 시작할 수 있습니다.

하드 디스크는 작업 도중에 대개 끼어들 수 없습니다. 더 급한 작업이 와도 지금 작업이 끝나기를 기다려야 합니다. CPU 는 거의 언제든 끼어들 수 있습니다(선점).

디스크 사용률이 60퍼센트를 넘으면 대기열 꼬리가 길어질 수 있습니다. 그러면 대기 지연이 더 자주, 더 눈에 띄게 생길 수 있습니다.

재는 창의 길이

긴 구간의 평균 사용률이 낮아도 짧게 몰린 사용률이 포화를 낳을 수 있습니다. 감시 도구 가운데는 사용률을 5분 평균으로 보고하는 것이 있습니다. 5분 평균은 몇 초씩 100퍼센트를 찍은 구간을 가릴 수 있습니다.

이런 사례가 있습니다. 한 고객의 감시 도구는 CPU 사용률이 80퍼센트를 넘은 적이 없다고 보여 줬습니다. 그런데도 그 서비스는 CPU 포화로 지연을 겪었습니다. 도구가 5분 평균을 보고하는 동안 CPU 사용률은 몇 초씩 100퍼센트를 찍고 있었습니다.

지연도 이른 신호가 됩니다. 지연이 오르는 것은 흔히 포화의 선행 지표입니다. 응답 시간을 짧은 것부터 늘어놓았을 때 99퍼센트 지점의 값을 99번째 백분위 응답 시간이라 합니다. 이 값을 1분 같은 짧은 창으로 재면 포화의 신호를 아주 일찍 받을 수 있습니다.

알림과 예측

포화는 사람을 부르는 문턱도 다릅니다. 골든 시그널 넷을 감시할 때 다른 신호는 문제가 된 때 사람을 부릅니다. 포화는 문제가 거의 된 때 부릅니다.

앞 소절의 선행 지표는 값이 오르는 순서의 이야기입니다. 이 소절의 문턱은 사람을 부르는 시점의 이야기입니다.

포화 감시는 곧 찰 것도 내다봅니다. 하드 디스크가 몇 시간 뒤 다 찰지 미리 셈하는 것이 그런 예측입니다.

복잡한 시스템에서는 더 높은 수준의 부하 측정으로 포화를 보충할 수 있습니다. 지금 트래픽의 두 배를 제대로 받아 낼 수 있나, 10퍼센트 더만 받을 수 있나, 지금 받는 양조차 다 감당하지 못하나를 묻는 식입니다.

커널에 임계 걸기

PSI 에는 임계를 넘으면 감시 프로그램을 깨워 주는 트리거가 있습니다. 트리거는 정해진 창 안에서 멈춘 시간의 누계가 얼마를 넘으면 깨울지를 정합니다.

등록하려면 /proc/pressure/ 아래 감시할 자원의 파일을 엽니다. 그 파일에 <some|full> <멈춘 시간 µs> <창 길이 µs> 꼴로 씁니다. 그다음 연 파일 서술자를 select() · poll() · epoll 로 기다립니다.

sequenceDiagram
    participant 감시 as 감시 프로그램
    participant 커널
    감시->>커널: /proc/pressure/memory 를 연다
    감시->>커널: some 150000 1000000 을 쓴다
    감시->>커널: poll() 로 기다린다
    Note over 커널: 1초 창 안에서 일부 작업의 메모리 정지가 150밀리초를 넘는다
    커널-->>감시: 깨운다

/proc/pressure/memory 에 some 150000 1000000 을 쓰면 1초 창 안에서 일부 작업의 메모리 정지가 150밀리초를 넘을 때 깨웁니다. 창 길이는 500밀리초에서 10초까지 받습니다.

관련 항목

포화와 함께 재는 감시 신호

골든 시그널 · 지연 · 트래픽 · 오류율 · 응답 시간 · 백분위수 · 꼬리 지연

포화를 재는 방법론과 지표

USE 방법 · 사용률 · PSI · vmstat · 실행 대기열 · 로드 애버리지 · iostat

포화가 드러나는 자원

CPU · 메모리 · 디스크 · 대역폭 · 커넥션 풀 · 스레드 풀 · 뮤텍스 · 파일 서술자

포화한 자원이 일으키는 현상

스와핑 · 스로틀링 · OOM 킬러 · 스래싱 · 큐 · 백프레셔

포화를 풀거나 미리 막는 수단

수평 확장 · 오토스케일링 · 부하 분산 · 로드 밸런서 · 캐싱 · 용량 계획 · 부하 테스트

포화를 설명하는 이론과 법칙

큐잉 이론 · 리틀의 법칙 · 병목 · 처리량 · 초당 요청 수

포화 지표를 내보내는 운영체제와 플랫폼

Linux · cgroup · Kubernetes · HikariCP

포화를 지켜보는 운영 체계

모니터링 · 관측성 · 알림 · SRE · 서비스 수준 목표

다른 이름: saturation · 새추레이션 · 포화 상태 · 자원 포화