디스크는 메모리와 속도차이가 많이 나고, OS에서는 이를 보완하기 위해 OS 캐시라는 것을 사용한다.
리눅스는 대표적으로 페이지 캐시 (Page Cache)를 갖고 있다.
페이지 캐시(Page Cache)
- 리눅스 커널이 디스크에서 읽거나 쓴 데이터를 메모리에 저장하는 방식.
- 주로 파일 시스템의 성능을 향상시키기 위해 사용됨.
- 디스크 I/O를 줄이고 성능을 최적화하는 역할.
페이지 캐시를 이해하기 위해서는 가상 메모리에 대해 좀 더 구체적으로 알아야 한다.
가상메모리
가상 메모리는 프로세스마다 독립적인 주소 공간을 제공하는 시스템입니다.
즉, 각 프로세스는 0x00000000~0xFFFFFFFF (예: 32비트 시스템) 범위의 주소를 사용할 수 있지만,
이 주소들은 실제 물리 메모리와 직접 매칭되지 않고 OS가 관리합니다.
✅ 가상 메모리의 핵심 역할
- 메모리 보호: 프로세스 간 메모리 접근을 차단하여 보안을 유지.
- 멀티태스킹 지원: 여러 프로세스가 동시에 실행될 수 있도록 지원.
- 효율적 메모리 사용: 스왑(Swap)과 조합하여 물리 메모리를 초과한 크기의 메모리를 제공.
🚀 왜 OS의 프로세스는 물리 주소가 아니라 가상 주소를 사용할까?
프로세스가 직접 물리 주소를 사용하지 않고 **가상 주소(Virtual Address)**를 사용하는 이유는 크게 메모리 보호, 효율적인 메모리 관리, 멀티태스킹 지원 때문입니다. 이를 하나씩 설명해볼게요! 😊
1️⃣ 보안과 메모리 보호
📌 문제: 프로세스가 직접 물리 주소를 사용하면?
- 프로세스가 직접 물리 주소를 접근하면 다른 프로세스의 메모리를 침범할 수 있음.
- 커널 영역까지 접근이 가능해져서 시스템이 크래시(Crash)할 위험이 커짐.
✅ 해결: 가상 메모리 사용
- 각 프로세스는 독립적인 가상 주소 공간을 가짐.
- 메모리 보호 기능을 통해 프로세스 간 간섭을 방지.
- 한 프로세스가 다른 프로세스의 메모리를 임의로 읽거나 수정할 수 없음.
💡 예를 들어, 웹 브라우저(Chrome)와 음악 앱이 동시에 실행된다고 가정하면,
웹 브라우저가 실수로 음악 앱의 데이터를 수정할 수 있으면 안 되겠죠?
그래서 각 프로세스는 자기만의 가상 주소 공간을 갖도록 강제됩니다.
2️⃣ 효율적인 메모리 관리
📌 문제: 물리 주소를 직접 쓰면?
- 프로그램이 실행될 때 물리 메모리(RAM)에 반드시 연속된 공간이 필요함.
- 하지만 메모리가 조각화(Fragmentation) 되면 할당이 어렵거나 불가능해짐.
✅ 해결: 가상 메모리 + 페이징(Paging)
- 가상 메모리를 사용하면 프로세스가 논리적으로 연속된 메모리처럼 보이지만,
실제 물리 메모리는 흩어져 있어도 문제없이 동작할 수 있음. - **페이지 테이블(Page Table)**을 통해 가상 주소와 실제 물리 주소를 매핑하여 조각화 문제 해결.
💡 예를 들어, 4GB 크기의 프로그램을 실행할 때,
물리 메모리에 연속된 4GB 공간이 없다면?
가상 메모리를 사용하면 조각난 물리 메모리에서도 실행 가능합니다!
3️⃣ 멀티태스킹 지원
📌 문제: 여러 프로세스가 동시에 실행될 때?
- 모든 프로세스가 물리 주소를 직접 사용하면 충돌이 발생함.
- 예를 들어, 두 개의 프로그램이 같은 물리 주소(예: 0x1000)에 데이터를 쓰려 하면?
- 데이터가 덮어씌워지는 메모리 충돌(Memory Conflict) 발생.
✅ 해결: 가상 메모리 사용
- 각 프로세스는 서로 독립적인 가상 주소 공간을 가지므로 충돌 없음.
- OS가 **MMU(Memory Management Unit)**를 이용해 각 프로세스의 가상 주소를 실제 물리 주소로 변환하여 충돌 방지.
💡 예를 들어, 프로세스 A와 B가 실행되더라도,
각각 0x1000이라는 가상 주소를 사용할 수 있지만,
OS가 내부적으로 다른 물리 주소에 매핑해서 충돌을 방지합니다.
4️⃣ 스왑(Swap) 및 확장성
📌 문제: 물리 메모리가 부족할 때?
- 프로그램이 사용하는 메모리가 많아지면 물리 메모리가 한계에 도달할 수 있음.
- 물리 주소를 직접 사용하면 메모리가 부족하면 실행 불가능.
✅ 해결: 가상 메모리 + 스왑(Swap)
- OS는 메모리가 부족하면, 일부 데이터를 디스크(스왑 영역)에 저장하여 공간을 확보할 수 있음.
- 가상 메모리를 사용하면 물리 메모리보다 더 큰 주소 공간을 사용할 수 있음.
💡 예를 들어, RAM이 8GB인데도 16GB짜리 프로그램이 실행 가능한 이유가 바로 가상 메모리 덕분입니다! 🎉
5️⃣ 코드 공유와 중복 방지
📌 문제: 같은 프로그램을 여러 개 실행할 때?
- 같은 프로그램을 여러 번 실행하면 메모리에 같은 코드가 여러 번 로드됨.
- 메모리 낭비 발생.
✅ 해결: 가상 메모리에서 코드 공유
- OS는 읽기 전용 코드(예: 실행 파일 코드)를 여러 프로세스가 공유할 수 있도록 매핑.
- 즉, 같은 프로그램이 여러 번 실행되더라도 한 번만 메모리에 로드되도록 최적화.
💡 예를 들어, Notepad를 5개 실행한다고 가정하면,
가상 메모리를 통해 같은 코드 영역을 공유하고,
각 프로세스의 데이터 영역만 따로 할당되어 메모리 사용을 최적화할 수 있습니다.
🎯 정리
❌ 물리 주소를 직접 쓰면?
- 메모리 보호 불가능 → 다른 프로세스의 메모리를 침범할 수 있음.
- 메모리 조각화 문제 → 연속된 공간이 필요하므로 메모리 낭비.
- 멀티태스킹 불가능 → 여러 프로세스가 같은 물리 주소를 쓰면 충돌.
- 스왑 기능 불가 → RAM이 부족하면 프로그램 실행이 불가능.
✅ 가상 주소를 사용하면?
- 보안과 안정성 보장 → 프로세스 간 메모리 보호.
- 효율적인 메모리 할당 → 조각난 물리 메모리에서도 실행 가능.
- 멀티태스킹 지원 → 같은 가상 주소를 사용해도 충돌 없음.
- 확장 가능 → RAM보다 큰 메모리를 사용할 수 있음 (스왑 사용 가능).
- 코드 공유 가능 → 같은 프로그램의 중복된 코드 로드를 방지.
페이징
가상 메모리를 물리 메모리에 효율적으로 매핑하기 위해 페이징(Paging) 기법이 사용됩니다.
🔹 페이징(Paging)이란?
페이징은 고정 크기(예: 4KB)의 블록(페이지, Page) 단위로 가상 주소를 물리 주소에 매핑하는 기법입니다.
✅ 페이징의 특징
- 가상 메모리를 일정한 크기의 "페이지(Page)"로 나눈다.
- 물리 메모리(RAM)도 동일한 크기의 "프레임(Frame)"으로 나눈다.
- 각 페이지가 비연속적인 프레임에 저장될 수 있도록 페이지 테이블(Page Table)을 통해 관리한다.
💡 즉, 가상 메모리 주소와 물리 메모리 주소가 1:1로 매칭되는 것이 아니라,
OS가 중간에서 페이지 테이블을 이용해 어디로 매핑할지 결정하는 구조입니다.
리눅스의 페이지 캐시 원리
📌 1. 페이지 캐시란?
**페이지 캐시(Page Cache)**는 리눅스 커널이 디스크에서 읽은 파일 데이터를 RAM에 저장하는 메모리 영역입니다.
즉, 자주 사용되는 파일 데이터를 디스크가 아닌 메모리에서 빠르게 제공하는 역할을 합니다.
✅ 주요 특징
- 파일 데이터를 RAM에 저장하여 디스크 I/O를 최소화.
- 파일 읽기(Read)와 쓰기(Write) 모두 캐싱 가능.
- 필요하지 않은 경우 자동으로 제거되어 메모리 낭비 없음.
- 모든 프로세스가 공유 가능 (같은 파일을 여러 프로세스가 열면 캐시된 데이터 활용).
디스크에서 파일 읽기 요청 발생 → OS가 페이지 캐시 확인 → 캐시에 있으면 RAM에서 바로 반환
🔍 2. 페이지 캐시 동작 원리
리눅스는 파일을 읽거나 쓸 때 직접 디스크에 접근하지 않고, 페이지 캐시를 먼저 확인합니다.
✅ 파일 읽기(READ) 과정
- 사용자가 cat file.txt 등의 명령어로 파일을 읽음.
- 커널이 페이지 캐시(Page Cache)를 먼저 확인.
- (캐시 적중, Cache Hit)
- 해당 데이터가 페이지 캐시에 있다면 디스크 접근 없이 바로 반환 → 속도 빠름! ⚡
- (캐시 미스, Cache Miss)
- 페이지 캐시에 없다면 디스크에서 데이터를 읽어와 페이지 캐시에 저장 후 반환.
cat file.txt 실행 → 페이지 캐시에 데이터가 있음 → RAM에서 직접 반환 (디스크 I/O 없음) ✅ → 페이지 캐시에 데이터가 없음 → 디스크에서 읽고 페이지 캐시에 저장 후 반환 🛠️
✅ 파일 쓰기(WRITE) 과정
파일을 수정하거나 저장할 때도 페이지 캐시를 사용하여 쓰기 성능을 최적화합니다.
- 사용자가 파일에 데이터를 씀 (echo "hello" > file.txt).
- 커널은 디스크에 직접 쓰지 않고, 페이지 캐시에 먼저 저장.
- 일정 시간이 지나거나(비동기 처리), 특정 조건이 충족되면(sync 호출) 디스크에 기록(Flush).
- Dirty Page(수정된 페이지):
- 디스크와 동기화되지 않은 변경된 페이지.
- Dirty Page는 sync, fsync(), flush 등에 의해 디스크로 반영됨.
echo "hello" > file.txt 실행 → 페이지 캐시에 먼저 저장 ✅ → 나중에 디스크에 쓰기(비동기 처리) 🛠️ → `sync` 호출 시 즉시 디스크로 반영 🔄
VFS (가상파일시스템)
디스크의 캐시는 페이지캐시에 의해 제공되지만 실제로는 VFS라는 가상파일시스템을 통해 인터페이스를 통일한다.
왜냐면 리눅스에는 ext3, ext2, ext4와 같은 파일시스템이 있는데 어떤 파일시스템을 쓰더라도 페이지 캐시 구조는 동일하게 적용되어야 한다.
이를 가능케하는것이 VFSdlek.
리눅스는 페이지 단위로 디스크를 캐싱한다
만약 물리메모리가 2GB 밖에 안되는데 파일크기가 4GB인 파일을 읽으려면 어떻게될까?
기본적으로 페이지 크기는 4KB이고, 페이지 단위로 캐싱을 하기 때문에 메모리에는 파일의 일부분만 불러와서 가상 메모리에 캐싱할 수 있다.
LRU (Least Recently Used)
메모리가 2GB이고 4GB 파일을 다 읽었다면 캐싱된 부분은 어떤 부분일까?
마지막에 읽은 부분이 캐싱되어 있다.
캐시를 전제로 한 I/O 줄이는 방법
데이터 규모에 비해 물리 메모리가 크다면 페이지 캐시에 의해 성능을 보완할 수 있다.
하지만 물리적 메모리가 부족할 경우 전체 데이터를 캐싱할 수 없기 때문에 대책이 필요하다
- 압축
- 서버 대수 늘리기 (scale-out)
이 중 scale-out 방식은 가장 간단하게 고려해볼 수 있는 방법이다. 하지만 이 방식으로 근본적인 극복이 불가능한 이유가 있다.
캐시는 서버에 저장되는 개념이므로 한 서버에서 캐싱이 안되어서 서버를 늘리더라도 그 요청을 다른 서버에서는 처리할 수가 없다.
이를 해결하기 위해 국소성 (locality) 라는 개념을 통해 접근해보아야 한다.
국소성 (locality)
**국소성(Locality)**이란
프로그램이 메모리를 접근할 때, 특정 영역을 집중적으로 사용하는 경향을 의미합니다.
즉, 최근에 접근한 데이터나 코드가 다시 사용될 가능성이 높다는 원칙을 따릅니다.
사용 예시
하테나 북마크 서비스의 "인기 엔트리" 페이지의 경우 인기 엔트리용 캐시 테이블을 많이 액세스하지만 자신의 북마크를 액세스 할 때 즉, id:na에 액세스 할때는 다른 데이터가 캐싱된다.
이 때 서버가 2대 있다고 가정하면 맘대로 분산처리 했을 때 "인기 엔트리" 페이지를 로드하면 서버1, 서버2에 모두 캐싱된다.
그래서 만약 메모리가 부족할 경우 자신의 북마크를 액세스할 때 캐싱이 되어있지 않으니 성능이 떨어질 수도 있는 여지가 생긴다.
반면 "인기 엔트리" 페이지로의 진입 요청을 서버1만 처리하도록 한다면 서버1에서는 "인기 엔트리" 페이지만 캐싱해놓으면 되고, 서버2에서는 상대적으로 캐시할 수 있는 공간이 늘어나게 된다.
이를 실현하기 위해 몇가지 방법이 있다.
파티셔닝
eg) 1대 였던 DB를 여러대의 서버로 분할 하는것. 이 때 분할의 단위는 DB 테이블이다.
예시) entry(10GB), bookmark(10GB), tag(4GB), keyword(4GB) 4개 테이블이 있다고 가정하자. 이 때 서버가 2대 있고, 메모리가 각각 16GB를 가졌다고 가정하자.
만약 이 상태에서 entry와 bookmark 테이블을 하나의 서버에 저장한다면 모든 데이터를 캐싱할 수 없다. 하지만 entry, tag를 서버1에 bookmark, keyword를 서버 2에 저장한다면 모든 데이터를 캐싱할 수 있게 된다. 이것이 파티셔닝의 원리임.
테이블 데이터 분할 (샤딩)
1개의 테이블을 쪼개는 것.
eg) a~c는 서버1에 저장. d~f는 서버2에 저장..
요청 패턴을 '섬'으로 분할
HTTP 요청의 User-Agent나 URL등을 보고 판단하는 방식
eg) 통상의 사용자이면 섬1, 일부 API요청이면 섬2, 봇의 요청이면 섬3으로 구분하여 각 서버가 처리하는 것.
사용자는 다양한 페이지를 액세스하고 봇은 특정 페이지만 액세스 하는 경향이 있다. 이 경향에 맞추어 각 서버를 구성하고 그 서버들이 섬을 각각 처리하는 것.
이렇게되면 캐싱하기 쉬운요청과 어려운 요청을 나누어 처리할 수 있으므로 국소성의 원리라고 할 수 있다.
'Computer Science' 카테고리의 다른 글
웹 서버와 WAS의 차이? (0) | 2024.10.26 |
---|---|
DB Lock (0) | 2024.10.07 |
빅오 (Big-O)에 대해.. (2) | 2024.10.06 |