백엔드 면접 용어 사전
운영체제

LRU

가장 오래 참조되지 않은 페이지를 내보내는 교체 정책. 해시맵 + 이중 연결 리스트로 O(1) 구현한다.

가장 오래 안 쓴 것부터 내보내는 교체 정책(Least Recently Used).

왜 이것이 합리적인가 — 지역성

과거 행동으로 미래를 예측하는 것이다.

시간 지역성 : 최근에 쓴 것은 곧 다시 쓸 가능성이 높다

  • (반복문 변수, 자주 조회하는 사용자 정보) 공간 지역성 : 그 근처도 곧 쓸 가능성이 높다

  • (배열 순회, 순차 파일 읽기)

시간 지역성에 근거한 것이 LRU다. 오래 안 쓴 것은 앞으로도 안 쓸 것이라 본다.

이론적 최적과의 관계

OPT(최적 교체) 는 "미래에 가장 나중에 쓸 페이지"를 내보낸다. 가장 좋지만 미래를 알아야 해서 구현 불가능하다. LRU는 이 OPT를 과거를 거울 삼아 근사한 것이다.

O(1) 구현 — 해시맵 + 이중 연결 리스트

면접 단골 구현 문제다.

  • 해시맵: key → 노드 주소 — (O(1) 탐색)

  • 이중 연결 리스트: 최근 사용 순 정렬 — (O(1) 이동·삭제)

    • head(최근) ⇄ [A] ⇄ [B] ⇄ [C] ⇄ tail(오래됨)
  • get(B) — → 해시맵으로 B 노드를 즉시 찾음 → 리스트에서 떼어 head로 이동

  • put(D) — → 가득 찼으면 tail 노드 제거 → head에 D 추가

왜 이중 연결이어야 하나 — 노드를 리스트 중간에서 떼어내려면 앞 노드를 알아야 한다. 단일 연결이면 앞을 찾는 데 O(n)이 든다.

왜 해시맵이 필요한가 — 리스트만으로는 특정 키를 찾는 데 O(n)이다.

두 자료구조의 약점을 서로 메우는 구조다. 자바에는 LinkedHashMap(capacity, 0.75f, true) 로 내장돼 있다 (세 번째 인자 accessOrder=true가 LRU 동작).

하드웨어에서는 근사만 쓴다

정확한 LRU는 접근할 때마다 순서를 갱신해야 한다. CPU 캐시나 페이지 테이블에서 매 접근마다 이러면 비용이 감당이 안 된다.

클럭 알고리즘(2차 기회) 이 실제로 쓰인다.

페이지마다 참조 비트 1개만 둔다 시곗바늘처럼 돌면서:

  • 참조 비트 = 1 → 0으로 바꾸고 지나감 (한 번 더 기회)
  • 참조 비트 = 0 → 이 페이지를 내보냄

비트 하나로 "최근에 썼는가"를 근사한다. 훨씬 싸면서 LRU와 비슷하게 동작한다.

LRU가 나쁜 경우 — 순차 스캔 오염

캐시 크기 100, 자주 쓰는 데이터 100개가 들어 있음
→ 배치 작업이 10만 건을 한 번씩 순차 조회
→ 캐시가 배치 데이터로 전부 교체됨 (한 번 쓰고 다시 안 쓸 데이터)
→ 원래 데이터는 전부 밀려남 → 이후 히트율 0%

한 번만 쓰이는 데이터가 자주 쓰이는 데이터를 밀어낸다.

대안

정책방식
LFU빈도 기준. 한 번 접근은 밀어내지 못함
LRU-KK번째 최근 접근을 기준 (1회성 접근에 강함)
2Q / SLRU신규 진입 구역과 보호 구역을 나눔
TinyLFU빈도를 확률적 자료구조로 추정 (Caffeine이 채택)

Redis는 allkeys-lruallkeys-lfu를 모두 제공한다. 배치가 도는 환경이면 LFU가 낫다.

MySQL InnoDB는 버퍼 풀을 young/old 두 구역으로 나눠(기본 5:3), 새로 읽은 페이지를 old 구간에 넣고 일정 시간 뒤 다시 접근돼야 young으로 올린다. 풀스캔이 버퍼 풀을 통째로 오염시키는 것을 막는 장치다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 운영체제 — 프로세스/스레드·동기화·가상메모리