LRU (Least Recently Used) 교체 알고리즘

핵심 인사이트 (3줄 요약)

  1. 본질: LRU(Least Recently Used)는 램(RAM)에 빈방이 없을 때 페이지를 쫓아내기 위해, **"과거에 가장 오랫동안(Least) 참조되지 않은(Recently Used) 페이지는 미래에도 안 쓸 것이다"**라는 강력한 통계적 가정을 바탕으로 희생양을 처형하는 교체 알고리즘이다.
  2. 가치: 불가능의 영역인 최적 교체(OPT, 미래 예측) 곡선에 가장 가깝게 들러붙는 압도적인 적중률을 자랑하며, 프레임을 늘리면 폴트가 오히려 늘어나는 바보 같은 벨라디의 모순(Belady's Anomaly)을 스택 성질(Stack Property)을 통해 완벽하게 박멸했다.
  3. 융합(한계): 이론상 완벽하지만 수십만 개의 페이지마다 '마지막 접근 시간'을 기록하거나 순서를 갱신해야 하는 끔찍한 오버헤드를 동반하므로, 현대 하드웨어와 OS는 이를 100% 쌩으로 돌리지 못하고 참조 비트 1개로 퉁치는 'Clock (2차 기회) 알고리즘'이라는 근사치(Approximation) 형태로 융합하여 실무에 적용한다.

Ⅰ. 개요 및 필요성 (Context & Necessity)

  • 개념: 물리 메모리가 꽉 차 스왑 아웃(Swap-out)을 시킬 때 타겟을 고르는 알고리즘이다. 무조건 오래된 걸 쫓아내는 FIFO와 달리, LRU는 '시간'이 아니라 '사용 내역(액티브)'을 본다. 10년 전에 들어왔어도 1초 전에 내가 읽었다면(Hit), 그 페이지는 다시 "가장 최근에 쓴 놈"으로 젊어지며 쫓겨날 확률 순위 맨 뒤로 생명 연장을 받는다.

  • 필요성: FIFO를 썼더니 맨 처음에 메모리에 올라온 '핵심 전역 변수'나 'Init() 함수'가 늙었다는 이유로 억울하게 쫓겨났다가 다시 램으로 올라오는 삽질(스래싱)이 무한 반복되었다. 공학자들은 "들어온 시간이 중요한 게 아니라, 방금 전까지 누가 얘를 계속 건드리고 있었느냐가 중요한 거 아니야?"라는 본질을 깨달았다. 즉, 프로그램의 코드가 for 루프를 돌며 계속 특정 변수만 괴롭히는 특성(지역성)을 캐치하여, "최근에 버림받은 놈을 죽이자"는 냉혹한 실용주의가 필요했다.

  • 💡 비유: LRU는 옷장의 계절 옷 정리법과 같다. FIFO 엄마는 "내가 10년 전에 사준 패딩이 제일 오래됐으니 갖다 버려!"라고 하지만, 아들은 "엄마 나 어제도 그 패딩 입었어! 버리면 얼어 죽어!"라고 울부짖는다(모순 터짐). 반면 LRU 엄마는 "이 반소매 티셔츠는 산 지 1달밖에 안 됐지만, 네가 겨울 내내 한 달 동안 '한 번도 안 입었으니(Least Recently)' 버려!"라고 한다. 이 옷장 정리가 훨씬 쾌적하고 내가 내일 입을 옷(미래)을 버릴 확률이 압도적으로 적다.

  • 등장 배경 및 OPT의 그림자:

    1. OPT(미래 투시)의 좌절: 미래를 아는 알고리즘은 코딩할 수 없다.
    2. 시간 대칭성 가설: "미래에 가장 늦게 쓰일 놈 = 과거에 가장 안 쓴 놈". 과거와 미래가 거울처럼 데칼코마니 대칭을 이룬다는 천재적 가설을 채택.
    3. 대성공과 오버헤드: 시뮬레이션 결과 OPT 턱밑까지 폴트를 막아냈으나, "과거를 완벽하게 기억하는 데 드는 비용(CPU 낭비)"이 너무 커 실무 도입에 장벽을 맞았다.
┌───────────────────────────────────────────────────────────────────────────┐
│        LRU 알고리즘의 동작 시각화 (스택 기반의 생명 연장 마술)            │
├───────────────────────────────────────────────────────────────────────────┤
│                                                                           │
│ [ 상황: 램 프레임 3개 / 호출 순서: 1 -> 2 -> 3 -> 1 -> 4 ]                │
│                                                                           │
│ ▶ 1. 초기 램 꽉 참                                                        │
│   (최근) [ 3 ] - [ 2 ] - [ 1 ] (가장 안 씀: 쫓겨날 1순위)                 │
│                                                                           │
│ ▶ 2. 기적의 생명 연장: CPU가 '1'을 다시 불렀다! (Hit!)                    │
│   OS: "어이구 1번 안 쓰는 놈인 줄 알았는데 방금 또 썼네? VIP로 모셔!"     │
│   (최근) [ 1 ] - [ 3 ] - [ 2 ] (가장 안 씀: 타겟이 2로 바뀜!)             │
│                                                                           │
│ ▶ 3. 새로운 위기: CPU가 '4'를 불렀다! (Page Fault 터짐)                   │
│   OS: "누굴 죽일까... 1은 방금 썼고, 3도 아까 썼고, 2가 젤 오래 쉬었네!"  │
│   '2'를 처형하고 디스크로 날림! 그 자리에 4를 얹음.                       │
│   (최근) [ 4 ] - [ 1 ] - [ 3 ] (가장 안 씀: 이번엔 3이 타겟)              │
│                                                                           │
│ ✅ 결과: 만약 FIFO였다면 2번 스텝에서 1을 죽이고 1을 또 가져오는 미친 짓을│
│         했겠지만, LRU는 방금 쓴 1을 살려내어 완벽하게 폴트를 방어함!      │
└───────────────────────────────────────────────────────────────────────────┘

[다이어그램 해설] LRU의 핵심은 "순위의 역동성"이다. 한 번 램에 들어왔다고 끝이 아니다. 불릴(Reference) 때마다 무조건 맨 앞줄(Top)로 새치기를 시켜준다. 이 쉼 없는 줄 세우기 덕분에 프로그램이 뺑글뺑글 도는 while 루프의 핵심 변수들은 영원히 쫓겨나지 않고 램의 1열을 독차지하게 되며, 시스템 성능이 극한으로 방어된다.

  • 📢 섹션 요약 비유: 서재에 책을 3권만 꺼내둘 수 있습니다. 책을 볼 때마다 다 본 책을 서재의 맨 왼쪽 끝(가장 최근)으로 밀어놓습니다. 며칠 지나면 자연스럽게 오른쪽 끝에는 '가장 오랫동안 안 편 먼지 쌓인 책(LRU 타겟)'이 밀려나 있게 되고, 새 책이 필요할 때 고민 1도 없이 맨 오른쪽 책을 창고로 치워버리는 우아한 정리법입니다.

Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)

LRU를 완벽하게 구현하기 위한 하드웨어의 눈물 2가지

LRU는 이론이 너무 예쁘지만, 저 "맨 앞으로 순위 당겨주기"를 매 클럭마다 컴퓨터가 하려면 피를 토해야 한다. 구현법은 두 가지가 제안되었다.

  1. 카운터 / 타임스탬프 (Counters) 방식

    • 구조: 페이지 테이블(PTE) 한 줄마다 '시간(Time)'을 기록하는 64비트 시계 변수를 달아놓는다.
    • 동작: CPU가 3번 페이지를 터치하는 0.000001초의 찰나에, 하드웨어가 3번 PTE에 들어가서 Time = 2026-03-23 15:30:11.999 라고 미친 듯이 시간을 갱신해 놓는다.
    • 교체 시: 램이 모자라면 OS가 400만 개의 장부를 풀 스캔(O(N))하며 "누구 시계가 제일 옛날이지?" 찾아서 죽인다.
    • 절망: 시간 갱신 오버헤드 + 풀 스캔 지연 = 시스템 즉사.
  2. 스택 (Stack) 하드웨어 방식

    • 구조: 방금 예시처럼 페이지 번호를 '더블 링크드 리스트(Double Linked List) 스택'으로 유지한다.
    • 동작: 5번 페이지를 읽으면, 리스트 중간에 박혀있던 5번의 앞뒤 포인터 6개를 지우고 잇고 난리 부르스를 쳐서 5번을 꼭대기(Top)로 뽑아 올린다.
    • 교체 시: 그냥 맨 밑바닥(Bottom)에 있는 놈을 $O(1)$ 속도로 바로 죽인다. (탐색 속도 우주 최강).
    • 절망: 메모리를 딱 1번 읽을 때마다 포인터 6개를 뜯어고쳐야 한다. CPU 속도가 반에 반 토막 난다.

그래서 현업(OS)은 어떻게 하는가? (포기 선언)

결론부터 말하면, 윈도우, 리눅스, 맥 OS 등 세상에 존재하는 그 어떤 범용 운영체제도 이 '완벽한 100% 순수 LRU'를 사용하지 않는다. 소프트웨어(OS)가 매 클럭마다 인터럽트를 걸어 순위를 바꾸는 건 미친 짓이고, 하드웨어(CPU)가 수백만 장의 스택 포인터를 실시간 조작할 회로를 납땜하는 것도 돈 낭비다. 결국 인류는 "완벽한 LRU는 포기하자. 그냥 1비트만 써서 대충 LRU 비스무리하게 흉내만 내는 가짜 LRU(Pseudo-LRU / Clock Algorithm)로 퉁치자!"라고 역사적 백기를 들게 된다.

  • 📢 섹션 요약 비유: 순수 LRU는 사장님이 전 직원 1만 명의 사원증 태그 시간을 초 단위로 기록해서, 어제 1초라도 더 일찍 퇴근한 놈을 오늘 자르는 숨 막히는 지옥입니다. 기록하다가 인사팀(OS)이 과로사합니다. 그래서 현실에서는 그냥 퇴근할 때 지문 1번(1비트)만 찍게 하고 대충 평가하는 근사치(Clock) 평가로 타협한 것입니다.

Ⅲ. 융합 비교 및 다각도 분석

비교 1: OPT (최적) vs LRU (과거) vs FIFO (먼저 옴)

가상 메모리 트로이카의 최종 결산 매트릭스.

알고리즘성능(적중률) 등급벨라디의 모순구현 가능성 (실무)
OPT (미래 투시)👑 1등 (넘사벽)🟢 절대 없음☠️ 절대 불가능 (타임머신 없음)
LRU (과거 추적)⭐ 2등 (OPT 턱밑)🟢 절대 없음🔴 가능은 한데 렉 너무 심해 근사치로 퉁침
FIFO (시간 맹신)💩 꼴찌 (자해 공갈)☠️ 펑펑 터짐🟢 눈 감고도 만듦. (하지만 안 씀)

스택 알고리즘 (Stack Algorithm)의 승리

LRU가 위대한 또 다른 이유는, 아무리 램 프레임 개수를 늘려다 꽂아줘도(3장 -> 4장 -> 5장) 절대로 폴트 횟수가 늘어나지 않는 '벨라디의 모순 박멸' 증명을 끝낸 우아한 수학적 모델이기 때문이다.

  • 방이 3장일 때 LRU 큐: [A, B, C]
  • 방이 4장일 때 LRU 큐: [A, B, C, D]
  • 방 크기가 커져도, 내가 소중하게 아끼는 상위 3등까지의 VIP(A, B, C)는 무조건 부분집합(Subset)으로 그대로 다 안고 간다. 그래서 램을 늘려주면 무조건 성능이 향상되는 우하향 폴트 곡선을 안정적으로 보장한다. 인프라 엔지니어가 믿고 램 증설 결재를 올릴 수 있는 든든한 빽이다.
┌──────────┬────────────┬────────────┬────────────────────────────┐
│ 평가 지표  │ 늙은 코어 코드│ 최신 잡다 코드│ 램 증설 시 효과    │
├──────────┼────────────┼────────────┼────────────────────────────┤
│ FIFO     │ 처참히 쫓겨남 │ 살아남아 낭비 │ 오히려 터질 수 있음  │
│ LRU      │ **영원히 생존**│ 금방 쫓겨남   │ **무조건 성능 향상**│
└──────────┴────────────┴────────────┴────────────────────────────┘

[매트릭스 해설] 운영체제의 핵심 철학은 "많이 쓰는 놈을 우대하라(Locality)"다. FIFO는 늙었다고 우대하지 않아 망했고, LFU(빈도)는 많이 썼다고 찌꺼기를 영원히 우대해서 망했다. LRU만이 오직 "지금 이 순간, 최근에 나를 찾아준 놈"만을 우대하여 변화무쌍한 프로그램의 페이즈 변환(Phase Transition)을 소름 끼치게 잘 따라간다.

  • 📢 섹션 요약 비유: FIFO가 오래된 친구(핵심 코드)를 늙었다고 배신하는 쓰레기라면, LRU는 10년 된 친구라도 어제 연락 한 번(Hit) 했으면 내 마음속 1순위로 다시 올려주는 진정한 의리의 알고리즘입니다.

Ⅳ. 실무 적용 및 기술사적 판단 (Strategy & Decision)

실무 시나리오: Redis, Memcached 인메모리 DB의 척추

운영체제 커널 레벨에서는 너무 무거워서 순수 LRU를 못 쓴다고 했다. 그러나 애플리케이션 레벨(User-space)에서는 이야기가 다르다!

  1. Redis의 위력: 백엔드 서버에서 램 20GB를 꽂고 캐시 서버(Redis)를 돌린다. 키(Key)가 1억 개가 넘어가며 20GB가 꽉 찬다.
  2. allkeys-lru 옵션의 발동:
    • Redis 설정 파일(redis.conf)에서 maxmemory-policy allkeys-lru를 켠다.
    • Redis는 1억 개의 키 값마다 24비트짜리 LRU 클럭(타임스탬프) 필드를 달아놓는다. 앱 단에서는 이 정도 오버헤드는 충분히 씹어먹는다.
    • 새로운 데이터가 들어와 램이 터지기 직전이 되면, Redis가 이 타임스탬프를 쓱 훑어보고(정확히는 샘플링) "가장 오랫동안 조회(GET)되지 않은 쓰레기 키"들을 팍팍 지워버리고 새 데이터를 받는다.
  3. 실무적 결단: 이 LRU 옵션 덕분에, 페이스북이나 인스타그램의 뉴스피드 캐시는 며칠 전의 낡은 글들은 자동으로 메모리에서 증발하고 방금 올라온 핫(Hot)한 게시물들로만 램을 100% 꽉 채우는 기적의 캐시 로테이션을 유지한다. 백엔드 캐싱 아키텍처의 알파요 오메가다.

안티패턴: LRU 캐시 오염 (Cache Pollution) 방어

만약 넷플릭스처럼 동영상을 '순차적'으로 한 번 쓱 긁고(Sequential Scan) 끝나는 데이터가 LRU를 덮치면 어떻게 될까? 거대한 10GB 영화 데이터가 램을 쓸고 지나가며, 내가 아끼던 VIP 캐시(자주 쓰는 썸네일, 로그인 정보)를 다 쫓아내고 10GB 전체가 LRU 맨 앞단을 장악한다. (그리고 그 영화는 다시 안 불린다). 이를 막기 위해 MySQL 등은 LRU 큐를 절반으로 찢어서(Midpoint), 한 번 긁고 버리는 뜨내기들은 절반 뒤쪽에서만 놀게 통제하여 VIP석을 지켜낸다.

  • 📢 섹션 요약 비유: 유명한 아이돌(핵심 데이터)로 가득 찬 1등석 차에, 1만 명의 시위대(동영상 스캔 데이터)가 우르르 탔다가 1초 만에 내리는 바람에 아이돌들이 다 밀려서 창밖으로 떨어져 버리는 참사(캐시 오염)를 막기 위해, 시위대는 무조건 뒷문으로 타서 뒤쪽에서만 머물게 하는 철통 방어 전략입니다.

Ⅴ. 기대효과 및 결론 (Future & Standard)

정량/정성 기대효과

구분내용
EAT(접근 시간) 최적 방어디스크 I/O를 일으키는 페이지 폴트($p$) 발생을 OPT 벤치마크에 버금가는 한 자릿수 오차율로 방어하여 서버를 구원
Locality(지역성)의 하드웨어적 증명"과거에 쓴 놈이 미래에도 쓴다"는 소프트웨어 패턴이 가상 메모리 관리에서 얼마나 강력한 무기인지 통계적으로 완벽히 입증
무수히 많은 변종 파생(Clock)순수 LRU의 끔찍한 오버헤드를 줄이기 위해 시곗바늘(Clock), 에이징(Aging) 등 수십 개의 경량화 변종을 파생시킨 캐시계의 아버지

결론 및 미래 전망

LRU (Least Recently Used) 교체 알고리즘은 인간이 발명한 "유한한 공간에 무한한 데이터를 쑤셔 넣는 방법" 중 통계적으로 가장 우아하고 강력한 마스터키다. 무식한 시간(FIFO)도, 고집스러운 횟수(LFU)도 아닌, "가장 최근의 쓰임(Recency)"에 모든 가중치를 두는 이 쿨(Cool)한 철학은 운영체제의 페이지 교체를 넘어 웹 브라우저 캐시, CDN 엣지 노드, 심지어 모바일 앱의 백그라운드 킬(Kill) 정책에까지 세상을 통치하는 스탠다드로 자리 잡았다. 비록 OS 커널 밑바닥에서는 하드웨어 칩셋의 한계로 인해 '1비트짜리 가짜 LRU(Clock)'로 타협하여 살아가고 있지만, 인공지능(AI) NPU가 보편화되는 미래에는 수백만 개의 타임스탬프를 0초 딜레이로 추론 연산해 내어 100% 퓨어(Pure) LRU, 나아가 완벽한 미래 예측(OPT)을 이뤄내는 하드웨어 혁명이 곧 도래할 것이다.

  • 📢 섹션 요약 비유: 완벽하게 이상형을 만날 수 있는 점쟁이(OPT)는 세상에 없지만, 그동안 내가 사귀었던 사람들의 데이터와 어제까지의 연애 패턴(LRU)을 철저히 분석해서 다음 사람을 고르는 것이, 가장 상처받지 않고(Page Fault 최소화) 현실에서 찾을 수 있는 최선의 짝 찾기 알고리즘인 것과 같습니다.

📌 관련 개념 맵 (Knowledge Graph)

  • 페이지 부재율 (Page Fault Rate) | LRU가 어떻게든 지수함수적으로 깎아내리려고 목숨을 걸고 발악하는 끔찍한 지연 확률 곡선
  • OPT 알고리즘 (Optimal) | LRU가 아무리 날고 기어도 절대 넘지 못하는, 100% 미래를 꿰뚫어 보는 벤치마크 절대신
  • 벨라디의 모순 (Belady's Anomaly) | FIFO를 썼을 때 터지는 엽기적인 렉 증가 현상으로, LRU의 '스택 성질' 덕분에 이 저주가 완벽히 파괴됨
  • Clock Algorithm (2차 기회) | LRU의 "타임스탬프 기록 오버헤드"가 너무 무거워 OS가 뻗어버리자, 1비트만 써서 LRU를 야매로 흉내 내는 현대 OS의 진정한 황제
  • Redis maxmemory-policy | 운영체제 밖에서도 백엔드 개발자가 캐시를 털어낼 때 숨 쉬듯 적용하는 LRU 철학의 실무 구현체

👶 어린이를 위한 3줄 비유 설명

  1. LRU 교체 알고리즘이 뭔가요? 장난감 상자가 꽉 찼을 때, "네가 가장 오~~랫동안 한 번도 안 만지고 구석에 방치해둔 장난감"을 골라서 버리는 똑똑한 정리법이에요.
  2. 먼저 들어온 걸 버리면 안 되나요? 제일 먼저 상자에 들어온 게 내가 매일 잘 때 안고 자는 '애착 인형(핵심 데이터)'일 수 있거든요! 낡았다고 버리면 내가 엉엉 울잖아요.
  3. 왜 똑똑한가요? "어제도 갖고 놀았던 건 내일도 갖고 놀 확률이 아주 높다(지역성)"는 마법의 비밀을 알고 있어서, 내가 정말 아끼는 장난감은 절대로 버려지지 않는답니다!