하드웨어 기초 #3 메모리 — RAM과 계층 구조, 스왑이 시작되면 벌어지는 일

6 분 소요

#2에서 CPU가 메모리를 기다리느라 노는 시간을 줄이려고 캐시를 둔다고 했습니다. 그 기다림의 대상이 이번 글의 주제인 메모리입니다. 메모리는 평소에는 조용하다가 부족해지는 순간 시스템 전체를 절벽 아래로 떨어뜨립니다. 그 동작을 이해하면 “메모리 사용률이 높은데 괜찮은가” 같은 질문에 측정으로 답할 수 있게 됩니다.

하드웨어 기초 시리즈에서 이번 글의 위치입니다.

RAM이란 무엇인가 #

**RAM(Random Access Memory)**은 CPU가 지금 쓰는 데이터를 올려두는 작업 공간입니다. 프로그램을 실행하면 그 코드와 데이터가 스토리지에서 RAM으로 올라오고, CPU는 RAM에 올라온 것을 읽고 씁니다.

RAM에는 두 가지 중요한 성질이 있습니다.

  • 휘발성 — 전원이 꺼지면 내용이 사라집니다. 그래서 영구 보관은 스토리지가 맡습니다.
  • 임의 접근 — 어느 위치든 거의 같은 속도로 읽습니다. 순서대로 훑어야 빠른 일부 스토리지와 다른 점입니다.

결국 RAM은 빠르지만 한정돼 있고 전원이 꺼지면 내용이 사라지는 공간입니다.

메모리 계층 #

컴퓨터의 저장 공간은 하나가 아니라 여러 층입니다. 위로 갈수록 빠르고 작고 비싸며, 아래로 갈수록 느리고 크고 쌉니다.

계층대략적인 접근 시간크기휘발성
레지스터1ns 미만수백 바이트휘발성
L1 캐시약 1ns수십 KB휘발성
L2 / L3 캐시수 ns ~ 수십 ns수 MB휘발성
RAM약 100ns수 GB ~ 수백 GB휘발성
SSD / NVMe수십 ~ 수백 μs수백 GB ~ 수 TB비휘발성
HDD수 ms수 TB비휘발성

숫자의 단위가 한 줄 내려갈 때마다 크게 뛴다는 점에 주목합니다. RAM은 약 100ns, SSD는 그보다 수백 배, HDD는 수만 배 느립니다.

격차를 체감하기 — RAM 접근을 1초로 환산하면
RAM 접근   100ns   →  1초
SSD 접근   100μs   →  약 17분
HDD 접근   10ms    →  약 28시간

이 격차가 다음 절의 핵심입니다. 데이터가 RAM에 있을 때와 디스크까지 내려가야 할 때의 차이가 이만큼 큽니다.

용량 , 대역폭 , 지연시간은 다른 축이다 #

메모리를 이야기할 때 세 가지가 자주 뭉뚱그려집니다. 구분해 두면 사양표가 다르게 읽힙니다.

부족하면
용량한 번에 올려둘 수 있는 양 (GiB)스왑이 시작됨
대역폭초당 옮길 수 있는 양 (GB/s)대량 처리에서 밀림
지연한 번 접근에 걸리는 시간 (ns)잦은 임의 접근에서 느려짐

운영에서 가장 자주 부딪히는 것은 용량입니다. 용량이 부족해지는 순간 시스템은 느린 디스크를 끌어쓰기 시작하고, 그때 성능이 무너집니다.

메모리가 부족하면 — 스왑 #

메모리가 부족해지면 운영체제는 당장 쓰지 않는 데이터를 디스크의 한 영역으로 옮겨 RAM에 자리를 만듭니다. 이 영역이 **스왑(swap)**이고, 옮기는 동작이 스와핑입니다. 리눅스의 스왑 설정은 RHEL 기초 #6에서 실제 명령으로 다룬 적이 있습니다.

스왑은 메모리 부족으로 프로세스가 죽는 것을 막아주는 안전장치입니다. 하지만 대가가 큽니다. 앞 절의 표를 떠올리면, RAM은 약 100ns, 디스크는 그보다 수백에서 수만 배 느립니다. 자주 쓰는 데이터가 스왑으로 밀려나면 CPU는 매번 디스크를 기다리게 됩니다.

스왑이 본격화되면
정상:   CPU → RAM (100ns)                응답 빠름
부족:   CPU → 스왑(디스크) (수 ms)        응답이 수천 배 느려짐
심화:   읽자마자 또 밀려남(스래싱)        시스템이 거의 멈춘 듯 보임

데이터를 디스크로 내렸다가 곧바로 다시 필요해지면 올리고, 또 다른 데이터를 내리는 일이 반복되는 상태를 **스래싱(thrashing)**이라고 합니다. 이때 CPU는 정작 계산보다 데이터를 옮기는 데 시간을 쓰고, 시스템은 멈춘 것처럼 느려집니다. “갑자기 서버가 먹통이 됐다"의 흔한 원인입니다.

OOM Killer — 리눅스의 최후 수단 #

스왑마저 가득 차고 메모리를 더 내줄 곳이 없으면 리눅스는 **OOM Killer(Out Of Memory Killer)**를 작동시킵니다. 메모리를 많이 쓰는 프로세스를 골라 강제로 종료해 시스템 전체가 멈추는 것을 막는 장치입니다.

운영에서 데이터베이스나 애플리케이션이 “원인 없이 죽었다"면 OOM Killer를 의심합니다. 로그에 Out of memory: Killed process 같은 기록이 남습니다. 해결은 메모리를 늘리거나, 프로세스의 메모리 사용을 줄이거나, 컨테이너라면 메모리 한도를 조정하는 방향입니다.

페이지 캐시 — 남는 메모리는 노는 것이 아니다 #

리눅스에서 free 명령을 보면 여유 메모리가 적어 보여 놀라는 경우가 많습니다. 대부분 정상입니다. 리눅스는 남는 RAM을 디스크 캐시(페이지 캐시)로 활용하기 때문입니다.

한 번 읽은 파일을 RAM에 캐시해 두면 다음에 같은 파일을 디스크까지 가지 않고 RAM에서 바로 줍니다. 이 캐시는 애플리케이션이 메모리를 필요로 하면 즉시 반납됩니다.

free -h를 읽는 법
              total   used   free   buff/cache   available
Mem:          16Gi    6Gi    0.5Gi   9.5Gi        9Gi
                                     └─ 캐시      └─ 실제로 쓸 수 있는 양

봐야 할 값은 free가 아니라 **available**입니다. buff/cache는 필요하면 회수되므로, available이 넉넉하면 메모리는 부족하지 않습니다. free가 0에 가깝다고 놀랄 필요가 없습니다.

자주 만나는 함정 #

“메모리 사용률 90%인데 증설해야 하나” #

사용률 숫자만으로는 알 수 없습니다. 그 90%에 페이지 캐시가 포함됐다면 정상입니다. available이 충분한지, 스왑이 실제로 일어나는지를 봐야 판단이 섭니다.

“스왑을 켜두면 안전하다” #

스왑은 죽는 것을 막아줄 뿐, 성능을 지켜주지는 않습니다. 스왑이 본격적으로 쓰이기 시작하면 이미 응답은 무너진 상태입니다. 응답 지연 알람과 스왑 사용량을 함께 봐야 합니다.

“메모리를 늘리면 빨라진다” #

#1에서 짚은 오해입니다. 부족할 때 늘리면 절벽을 피하지만, 충분한 상태에서 더 늘린다고 빨라지지는 않습니다.

“컨테이너는 호스트 메모리를 다 쓸 수 있다” #

한도를 걸지 않으면 한 컨테이너가 호스트 메모리를 모두 삼켜 다른 컨테이너까지 위협합니다. 운영에서는 메모리 한도를 명시하는 것이 안전합니다. 컨테이너의 자원 격리는 #7에서 다루겠습니다.

정리 #

이번 글에서 잡은 그림입니다.

  • RAM은 CPU의 작업 공간입니다. 빠르지만 휘발성이고 한정돼 있습니다.
  • 저장 공간은 레지스터 → 캐시 → RAM → SSD → HDD의 계층이고, 한 층 내려갈 때마다 속도 차가 크게 벌어집니다.
  • 메모리는 용량,대역폭,지연이 서로 다른 축입니다. 운영에서 가장 자주 부딪히는 것은 용량입니다.
  • 용량이 부족하면 스왑이 시작되고, 심해지면 스래싱으로 시스템이 멈춘 듯 느려집니다.
  • 더 내줄 메모리가 없으면 OOM Killer가 프로세스를 강제 종료합니다.
  • 리눅스는 남는 RAM을 페이지 캐시로 씁니다. free가 아니라 **available**을 봐야 합니다.

다음 — 스토리지 #

메모리가 부족할 때 시스템이 빠지는 곳이 디스크였습니다. 그 디스크가 다음 두 글의 주제입니다. #4 스토리지 ① 장치 — HDD / SSD / NVMe와 IOPS / 처리량 / 지연시간에서는 디스크 한 장의 종류와 성능 지표를 정리하겠습니다. HDD와 SSD와 NVMe가 무엇이 다른지, 그리고 용량과 자주 혼동되는 IOPS,처리량,지연시간을 구분하겠습니다.

X