LRU
LRU (Least Recently Used)
1. 개요
LRU(Least Recently Used)는 캐시 교체 알고리즘의 하나로, 가장 오랫동안 참조되지 않은 데이터를 우선적으로 제거하여 새로운 데이터를 수용하는 메모리 관리 전략이다. 이 알고리즘은 "최근에 사용된 데이터가 가까운 미래에 다시 사용될 가능성이 높다"는 가정하에 동작하며, 한정된 캐시 공간을 효율적으로 활용하기 위해 사용된다.
2. 동작 원리
LRU의 핵심은 데이터의 접근 순서(Recency)를 기록하고 관리하는 것이다. 데이터에 접근할 때마다 해당 데이터를 '가장 최근에 사용됨' 상태로 업데이트하며, 캐시가 가득 찬 상태에서 새로운 데이터를 삽입해야 할 경우, 가장 오랫동안 접근되지 않은(Least Recently Used) 데이터를 삭제한다.
동작 단계
- 데이터 조회(Cache Hit 시): 요청한 데이터가 캐시에 존재하면, 해당 데이터를 리스트의 최상단(Most Recently Used)으로 이동시킨다.
- 데이터 삽입(Cache Miss 시): 요청한 데이터가 캐시에 없으면 새로운 데이터를 생성하여 최상단에 추가한다.
- 교체(Eviction): 삽입 시 캐시 용량이 초과되었다면, 리스트의 최하단(Least Recently Used)에 위치한 데이터를 제거한다.
캐시 상태 변화 예시 (캐시 크기: 3)
| 시간(t) | 요청 데이터 | 캐시 상태 (최신 $\rightarrow$ 오래된 순) | 결과 | 비고 |
|---|---|---|---|---|
| $t_1$ | A | [A] | Miss | A 삽입 |
| $t_2$ | B | [B, A] | Miss | B 삽입 |
| $t_3$ | C | [C, B, A] | Miss | C 삽입 (Full) |
| $t_4$ | A | [A, C, B] | Hit | A를 최신 상태로 이동 |
| $t_5$ | D | [D, A, C] | Miss | 가장 오래된 B 제거 후 D 삽입 |
| $t_6$ | B | [B, D, A] | Miss | 가장 오래된 C 제거 후 B 삽입 |
3. 구현 방법
LRU를 효율적으로 구현하기 위해서는 조회(Lookup)와 순서 변경(Update) 작업이 모두 빠르게 이루어져야 한다. 이를 위해 일반적으로 [해시 맵]과 [이중 연결 리스트]를 조합하여 사용한다.
- [[해시 맵]]: 특정 키(Key)를 통해 리스트의 노드에 즉시 접근할 수 있게 하여 조회 시간 복잡도를 $O(1)$로 줄인다.
- [[이중 연결 리스트]]: 노드의 추가, 삭제, 위치 이동을 $O(1)$에 수행할 수 있어 데이터의 최신 순서를 관리하기에 적합하다.
구현 구조 (Python 예시)
class Node:
def __init__(self, key, value):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.cache = {} # Hash Map: {key: Node}
self.head = Node(0, 0) # Dummy Head (MRU)
self.tail = Node(0, 0) # Dummy Tail (LRU)
self.head.next = self.tail
self.tail.prev = self.head
def _remove(self, node):
p, n = node.prev, node.next
p.next, n.prev = n, p
def _add(self, node):
# 항상 head 바로 뒤(최신 위치)에 추가
p, n = self.head, self.head.next
p.next = n.prev = node
node.prev, node.next = p, n
def get(self, key):
if key in self.cache:
node = self.cache[key]
self._remove(node)
self._add(node) # 접근했으므로 최신 상태로 갱신
return node.value
return -1
def put(self, key, value):
if key in self.cache:
self._remove(self.cache[key])
node = Node(key, value)
self._add(node)
self.cache[key] = node
if len(self.cache) > self.capacity:
# 더미 테일(Dummy Tail) 바로 앞의 노드가 가장 오래된 데이터임
lru = self.tail.prev
self._remove(lru)
del self.cache[lru.key]
4. 시간 및 공간 복잡도
캐시 용량을 $N$이라고 할 때, LRU의 복잡도는 다음과 같다.
| 작업 | 시간 복잡도 | 설명 |
|---|---|---|
| 조회 (Get) | $O(1)$ | 해시 맵을 통해 노드 위치를 즉시 탐색 |
| 삽입/삭제 (Put) | $O(1)$ | 해시 맵 업데이트 및 연결 리스트 포인터 조작 |
| 공간 복잡도 | $O(N)$ | 해시 맵과 연결 리스트에 각각 $N$개의 요소 저장 |
5. 장단점 및 한계
이론적 배경: [[시간 지역성]] (Temporal Locality)
LRU는 시간 지역성 원리에 기반한다. 시간 지역성이란 "최근에 참조된 메모리 위치가 가까운 미래에 다시 참조될 가능성이 높다"는 컴퓨터 과학의 관찰 결과이다. 예를 들어, 루프(Loop) 문 내의 변수나 재귀 함수의 지역 변수는 짧은 시간 동안 반복적으로 접근되는데, LRU는 이러한 특성을 이용하여 적중률(Hit Rate)을 높인다.
장점
- 높은 효율성: 구현이 비교적 단순하면서도, 대부분의 일반적인 워크로드에서 매우 높은 캐시 적중률을 보인다.
- 최신 트렌드 반영: 데이터의 접근 패턴이 시간에 따라 변하는 경우, 최근에 사용된 데이터를 유지함으로써 변화하는 워크로드에 빠르게 적응한다.
- 예측 가능성: 시간 지역성이 뚜렷한 데이터 패턴(예: 최근 뉴스 기사 조회, 최근 열어본 파일 등)에서 최적의 성능을 낸다.
단점 및 한계: 캐시 오염 (Cache Pollution)
- 순차적 스캔(Sequential Scan) 취약성: 매우 큰 데이터를 한 번만 훑고 지나가는 작업이 발생하면, 기존의 유용한 데이터들이 모두 밀려나고 한 번만 쓰일 데이터로 캐시가 채워지는 '캐시 오염' 현상이 발생한다. 예를 들어, DB에서 전체 테이블 스캔(Full Table Scan)을 수행할 때 빈번하게 발생하며, 이로 인해 정작 자주 사용되던 핫 데이터(Hot Data)가 제거되어 전체적인 성능이 저하된다.
- 최악의 경우 (Thrashing): 캐시 크기가 $N$일 때, $N+1$개의 데이터를 순차적으로 반복해서 요청하면 모든 요청이 Miss가 발생하는 현상이 나타나 캐시가 무용지물이 된다.
6. 활용 사례 및 변형 알고리즘
실제 적용 사례: OS 페이지 교체
운영체제(OS)의 가상 메모리 관리에서 물리 메모리가 부족할 때 어떤 페이지를 디스크로 내보낼지 결정하는 페이지 교체 알고리즘으로 LRU가 사용된다. - 시나리오: 프로세스가 메모리 페이지 A $\rightarrow$ B $\rightarrow$ C 순으로 접근했고, 메모리 용량이 2페이지라면, 새로운 페이지 D를 요청했을 때 가장 오래전에 사용된 A를 교체(Swap-out)하고 D를 적재한다. - 실제 구현: 하드웨어적으로 완벽한 LRU를 구현하는 것은 오버헤드가 크기 때문에, 실제 OS에서는 참조 비트(Reference Bit)를 활용한 LRU 근사 알고리즘(Clock Algorithm)을 주로 사용한다.
LRU vs [[LFU]] 비교
| 구분 | LRU (Least Recently Used) | LFU (Least Frequently Used) |
|---|---|---|
| 핵심 기준 | 시간 (Recency) | 빈도 (Frequency) |
| 제거 대상 | 가장 오랫동안 사용되지 않은 데이터 | 사용 횟수가 가장 적은 데이터 |
| 강점 | 최근 트렌드 반영에 유리함 | 장기적으로 자주 쓰이는 데이터 보존에 유리함 |
| 약점 | 일시적인 대량 스캔에 취약함 | 과거에 많이 쓰였으나 현재 안 쓰이는 데이터가 잔류함 |
변형 알고리즘
LRU의 한계를 극복하기 위해 다음과 같은 발전된 알고리즘들이 제안되었다. - LRU-K: 최근 $K$번의 접근 시간을 기록하여, 단순 최신성뿐만 아니라 빈도까지 고려함으로써 한두 번의 일시적인 접근으로 데이터가 캐시에 상주하는 문제를 해결한다. - ARC (Adaptive Replacement Cache): LRU와 LFU의 장점을 결합하여, 워크로드에 따라 두 정책의 비중을 동적으로 조절함으로써 다양한 접근 패턴에 유연하게 대응한다. - 2Q: 입구 큐를 두어 한 번만 접근된 데이터가 메인 캐시를 오염시키는 'Scan' 문제를 방지하고, 실제로 여러 번 참조된 데이터만 메인 캐시로 이동시킨다.
분류: 기술 / 캐시 관리 / 치환 정책
이 문서는 AI 모델(gemma-4-31b)에 의해 생성된 콘텐츠입니다.
주의사항: AI가 생성한 내용은 부정확하거나 편향된 정보를 포함할 수 있습니다. 중요한 결정을 내리기 전에 반드시 신뢰할 수 있는 출처를 통해 정보를 확인하시기 바랍니다.