캐시와 버퍼: 속도 차이를 극복하는 두 가지 방법
목차
1. 들어가며
컴퓨터 시스템에서 ‘캐시(Cache)‘와 ‘버퍼(Buffer)‘는 모두 데이터를 임시로 저장하는 메모리 공간이에요. 하지만 그 목적과 동작 방식은 근본적으로 다릅니다. 캐시는 속도 향상을 위해 자주 사용되는 데이터를 빠른 메모리에 저장하고, 버퍼는 속도 차이 조절을 위해 생산자와 소비자 사이에서 데이터를 임시 보관해요.
이 두 개념은 CS 면접에서 자주 등장하는 주제이며, 실무에서도 성능 최적화와 시스템 설계에 필수적인 개념입니다.
출처: GeeksforGeeks - Difference between Cache and Buffer
2. 메모리 계층 구조
컴퓨터의 메모리는 계층적 구조로 설계되어 있어요. CPU에 가까울수록 빠르지만 용량이 작고 비싸며, 멀어질수록 느리지만 용량이 크고 저렴합니다.

캐시는 이 계층 구조에서 상위 계층과 하위 계층 사이의 속도 차이를 줄이기 위해 존재합니다. 자주 접근하는 데이터를 빠른 메모리에 복사해두면, 느린 메모리에 접근하는 횟수를 줄일 수 있어요.
3. 캐시 메모리란
캐시의 목적
캐시는 데이터 접근 속도를 향상시키기 위한 고속 메모리예요. CPU가 메인 메모리(RAM)에서 데이터를 읽어오는 데는 상대적으로 많은 시간이 걸리거든요. 만약 자주 사용되는 데이터를 CPU와 더 가까운 곳에 복사해둔다면, 훨씬 빠르게 접근할 수 있습니다.
핵심 특징:
- 원본 데이터의 복사본을 저장해요
- 읽기 성능 향상이 주목적이에요
- 데이터가 없어도 원본에서 다시 가져올 수 있어요
- 투명하게 동작합니다 (애플리케이션이 의식하지 못함)
출처: 나무위키 - “캐시 메모리”
캐시 계층 (L1, L2, L3)
현대 CPU는 여러 단계의 캐시를 가지고 있어요.
L1 캐시 (Level 1 Cache)
CPU 코어에 가장 가까운 캐시로, 명령어 캐시(I-Cache)와 데이터 캐시(D-Cache)로 분리되어 있어요.
L1 캐시 구조:

하버드 아키텍처(Harvard Architecture)를 따라 명령어와 데이터를 분리함으로써, CPU가 동시에 명령어를 읽고 데이터를 처리할 수 있습니다.
출처: Wikipedia - CPU Cache, GeeksforGeeks - Cache Memory in Computer Organization
L2 캐시 (Level 2 Cache)
L1 캐시보다 크지만 약간 느린 캐시예요. 보통 각 CPU 코어마다 독립적으로 존재합니다.

L3 캐시 (Level 3 Cache)
모든 CPU 코어가 공유하는 캐시예요. 가장 크지만 L1, L2보다 느립니다.

지역성 원리 (Principle of Locality)
캐시가 효과적으로 동작하는 이유는 프로그램이 **지역성(Locality)**을 가지기 때문이에요.
시간 지역성 (Temporal Locality)
최근에 접근한 데이터는 가까운 미래에 다시 접근할 가능성이 높습니다.
// 시간 지역성 예시int sum = 0;for (int i = 0; i < 1000; i++) { sum += array[i]; // sum 변수는 반복적으로 접근됨}변수 sum은 루프 동안 계속 재사용되므로, 캐시에 저장해두면 효율적이에요.
출처: Carnegie Mellon University - Introduction to Computer Systems (15-213 강의 자료)
공간 지역성 (Spatial Locality)
최근에 접근한 데이터의 주변 데이터에 접근할 가능성이 높아요.
// 공간 지역성 예시int[] array = new int[1000];for (int i = 0; i < 1000; i++) { sum += array[i]; // 연속된 메모리 주소 접근}배열은 메모리에 연속적으로 저장되므로, array[0]을 캐시에 가져올 때 주변의 array[1], array[2]도 함께 가져옵니다 (캐시 라인 단위).
캐시 라인 (Cache Line): 캐시는 데이터를 개별 바이트가 아닌 블록 단위로 가져와요. 일반적으로 64바이트 단위의 캐시 라인을 사용합니다.

출처: Wikipedia - CPU Cache, GeeksforGeeks - Locality of Reference
4. 버퍼란
버퍼의 목적
버퍼는 속도 차이가 있는 두 장치 사이에서 데이터를 임시 저장하는 공간이에요. 생산자(Producer)가 데이터를 생성하는 속도와 소비자(Consumer)가 데이터를 처리하는 속도가 다를 때, 그 차이를 완충합니다.
핵심 특징:
- 데이터의 이동을 관리해요 (복사가 아님)
- 쓰기 성능 향상이 주목적이에요
- 데이터 손실 방지
- 명시적으로 관리됩니다 (애플리케이션이 의식함)
버퍼의 종류
키보드 버퍼
사용자가 키를 누르는 속도와 프로그램이 입력을 처리하는 속도 사이의 간격을 메워줍니다.

사용자 입력: H → e → l → l → o [Enter] ↓키보드 버퍼: [H][e][l][l][o][\n] ↓프로그램: "Hello\n" 읽기키보드 버퍼가 없다면, 프로그램이 입력을 읽기 전에 사용자가 누른 키가 손실될 수 있어요.
디스크 버퍼 (Disk Buffer)
디스크는 RAM보다 훨씬 느려요. 데이터를 디스크에 쓸 때마다 기다리면 프로그램이 멈추게 됩니다.
// 버퍼 없이 디스크 쓰기for (int i = 0; i < 1000; i++) { disk.write(data[i]); // 매번 디스크 접근 (매우 느림)}
// 버퍼를 사용한 디스크 쓰기BufferedWriter writer = new BufferedWriter(new FileWriter("file.txt"));for (int i = 0; i < 1000; i++) { writer.write(data[i]); // 버퍼에 쓰기 (빠름)}writer.flush(); // 버퍼의 내용을 한 번에 디스크로버퍼를 사용하면 여러 번의 작은 쓰기를 모아서 한 번의 큰 쓰기로 처리할 수 있어요.
출처: Java Documentation - BufferedWriter, GeeksforGeeks - BufferedWriter in Java
네트워크 버퍼 (Network Buffer)
네트워크 통신에서 송신 버퍼와 수신 버퍼를 사용해요.

수신 버퍼는 네트워크에서 데이터가 도착하는 속도와 애플리케이션이 데이터를 읽는 속도의 차이를 흡수합니다.
// TCP 소켓 버퍼 크기 설정Socket socket = new Socket();socket.setSendBufferSize(65536); // 송신 버퍼: 64KBsocket.setReceiveBufferSize(65536); // 수신 버퍼: 64KB출처: Java Documentation - Socket, GeeksforGeeks - Socket Programming in Java
링 버퍼 (Ring Buffer / Circular Buffer)
고정 크기의 버퍼를 원형으로 사용하는 자료구조예요.

링 버퍼는 포인터가 끝에 도달하면 처음으로 돌아가므로, 메모리를 재활용할 수 있습니다.
typedef struct { char buffer[BUFFER_SIZE]; int read_pos; int write_pos; int count; // 현재 저장된 데이터 개수} RingBuffer;
void ring_buffer_write(RingBuffer* rb, char data) { if (rb->count < BUFFER_SIZE) { rb->buffer[rb->write_pos] = data; rb->write_pos = (rb->write_pos + 1) % BUFFER_SIZE; rb->count++; }}
char ring_buffer_read(RingBuffer* rb) { if (rb->count > 0) { char data = rb->buffer[rb->read_pos]; rb->read_pos = (rb->read_pos + 1) % BUFFER_SIZE; rb->count--; return data; } return -1; // 버퍼가 비어있음}출처: Wikipedia - Circular Buffer, GeeksforGeeks - Circular Queue
5. 캐시 vs 버퍼: 핵심 차이
| 비교 항목 | 캐시 (Cache) | 버퍼 (Buffer) |
|---|---|---|
| 목적 | 속도 향상 (느린 메모리 접근 줄이기) | 속도 차이 조절 (생산자-소비자 동기화) |
| 데이터 특성 | 원본 데이터의 복사본 | 이동 중인 데이터 |
| 데이터 수명 | 원본이 변경되면 무효화 가능 | 읽으면 소비됨 (일회성) |
| 크기 | 상대적으로 작음 (용량 제약) | 상대적으로 유연함 |
| 관리 주체 | 하드웨어/시스템 (자동) | 소프트웨어 (명시적) |
| 주요 동작 | 읽기(Read) 최적화 | 쓰기(Write) 최적화 |
| 투명성 | 투명함 (애플리케이션이 모름) | 명시적 (애플리케이션이 관리) |
| 예시 | CPU 캐시, 브라우저 캐시, DNS 캐시 | 키보드 버퍼, 디스크 버퍼, 네트워크 버퍼 |
출처: Stack Overflow - What is the difference between buffer and cache?, GeeksforGeeks - Difference between Cache and Buffer
메모리 관점에서의 차이
리눅스의 free 명령어를 실행하면 캐시와 버퍼가 별도로 표시돼요.
$ free -h total used free shared buff/cache availableMem: 15Gi 8.0Gi 2.0Gi 1.0Gi 5.0Gi 6.0GiSwap: 2.0Gi 0B 2.0Gi- buff: 블록 디바이스의 메타데이터 버퍼 (파일 시스템 메타데이터)
- cache: 페이지 캐시 (파일 내용)
파일 읽기 과정:

출처: Linux man pages - free(1), Red Hat - Understanding Memory Usage on Linux
6. 캐시 동작 원리
Write-Through vs Write-Back
캐시에 데이터를 쓸 때 두 가지 정책이 있어요.
Write-Through (즉시 쓰기)
캐시와 메인 메모리에 동시에 씁니다.

장점:
- 데이터 일관성 유지 (캐시와 메모리가 항상 동일)
- 간단한 구현
단점:
- 쓰기가 느림 (매번 메모리 접근)
- 쓰기 성능 저하
출처: GeeksforGeeks - Write Through and Write Back in Cache, Wikipedia - Cache (computing)
Write-Back (나중에 쓰기)
캐시에만 쓰고, 나중에 캐시 라인이 교체될 때 메모리에 씁니다.

장점:
- 쓰기가 빠름 (캐시에만 쓰면 완료)
- 여러 번 쓰기를 한 번에 처리 가능
단점:
- 데이터 불일치 가능 (캐시와 메모리가 다름)
- Dirty Bit 관리 필요
출처: GeeksforGeeks - Write Through and Write Back in Cache, Carnegie Mellon University - Cache Memories
Dirty Bit
Dirty Bit는 캐시 라인이 수정되었는지 표시하는 플래그예요.
캐시 라인 구조:
동작 과정:
- 캐시 라인을 메모리에서 읽어올 때:
Dirty Bit = 0(Clean) - CPU가 캐시 라인에 쓰기를 할 때:
Dirty Bit = 1(Dirty) - 캐시 라인을 교체할 때:
Dirty Bit = 0: 그냥 교체 (메모리와 동일하므로)Dirty Bit = 1: 메모리에 쓴 후 교체 (Write-Back)
7. 버퍼 동작 원리
Producer-Consumer 패턴
버퍼는 전형적으로 생산자-소비자 문제에서 사용돼요.
// 공유 버퍼class SharedBuffer { private Queue<Integer> buffer = new LinkedList<>(); private int capacity;
public SharedBuffer(int capacity) { this.capacity = capacity; }
// 생산자: 데이터 생성 public synchronized void produce(int data) throws InterruptedException { while (buffer.size() == capacity) { wait(); // 버퍼가 가득 찼으면 대기 } buffer.add(data); System.out.println("생산: " + data); notifyAll(); // 소비자 깨우기 }
// 소비자: 데이터 소비 public synchronized int consume() throws InterruptedException { while (buffer.isEmpty()) { wait(); // 버퍼가 비었으면 대기 } int data = buffer.poll(); System.out.println("소비: " + data); notifyAll(); // 생산자 깨우기 return data; }}
// 생산자 스레드class Producer extends Thread { private SharedBuffer buffer;
public void run() { for (int i = 0; i < 10; i++) { try { buffer.produce(i); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }}
// 소비자 스레드class Consumer extends Thread { private SharedBuffer buffer;
public void run() { for (int i = 0; i < 10; i++) { try { buffer.consume(); Thread.sleep(200); // 생산자보다 느림 } catch (InterruptedException e) { e.printStackTrace(); } } }}동작 흐름:

생산자가 소비자보다 빠르더라도, 버퍼가 중간에서 데이터를 보관하므로 손실 없이 처리할 수 있습니다.
출처: GeeksforGeeks - Producer Consumer Problem in Java, Oracle - Java Concurrency Utilities
버퍼 오버플로우 (Buffer Overflow)
버퍼의 크기를 초과하여 데이터를 쓰면 버퍼 오버플로우가 발생해요.
char buffer[10];strcpy(buffer, "This is a very long string"); // 버퍼 오버플로우!
// buffer[10]을 넘어서는 데이터가 인접 메모리를 덮어씀메모리 구조:

버퍼 오버플로우는 심각한 보안 취약점으로, 공격자가 리턴 주소를 조작하여 악성 코드를 실행할 수 있습니다.
방어 기법:
// 안전한 방법 1: 크기 제한strncpy(buffer, input, sizeof(buffer) - 1);buffer[sizeof(buffer) - 1] = '\0';
// 안전한 방법 2: 경계 검사if (strlen(input) < sizeof(buffer)) { strcpy(buffer, input);}
// 안전한 방법 3: 안전한 함수 사용strlcpy(buffer, input, sizeof(buffer)); // BSD 시스템8. 실무에서의 사용 사례
캐시 사용 사례
1. 웹 브라우저 캐시

2. Redis 캐시
// 데이터베이스 조회 전 캐시 확인public User getUserById(String userId) { // 1. 캐시 확인 User user = redisTemplate.opsForValue().get("user:" + userId);
if (user != null) { return user; // 캐시 히트 }
// 2. 캐시 미스: DB 조회 user = userRepository.findById(userId);
// 3. 캐시에 저장 (TTL: 1시간) redisTemplate.opsForValue().set("user:" + userId, user, 1, TimeUnit.HOURS);
return user;}성능 개선:
- 캐시 히트 시: 1-5ms
- DB 조회: 50-200ms
- 10배 이상 성능 향상
출처: Redis Documentation - Caching Patterns, Baeldung - Spring Cache with Redis
3. CDN (Content Delivery Network)
사용자 (한국) → 오리진 서버 (미국) - 지연 시간: 200ms - 대역폭 비용: 높음
CDN 도입 후:사용자 (한국) → CDN 엣지 서버 (서울) - 지연 시간: 10ms - 대역폭 비용: 낮음 - 오리진 서버 부하 감소버퍼 사용 사례
1. 로그 버퍼링
// 버퍼링 없이 로그 작성 (느림)public void logWithoutBuffer(String message) { fileWriter.write(message + "\n"); fileWriter.flush(); // 매번 디스크 쓰기}
// 버퍼링을 사용한 로그 작성 (빠름)public class BufferedLogger { private BufferedWriter writer; private int bufferSize = 8192; // 8KB 버퍼
public void log(String message) throws IOException { writer.write(message + "\n"); // 버퍼가 가득 차면 자동으로 flush }
public void close() throws IOException { writer.flush(); // 남은 데이터를 디스크로 writer.close(); }}성능 비교:
- 버퍼 없음: 10,000개 로그 → 5초
- 버퍼 사용: 10,000개 로그 → 0.5초
- 10배 성능 향상
출처: Oracle - Java Performance Tuning Guide, Baeldung - Java BufferedWriter
2. 비디오 스트리밍 버퍼
비디오 플레이어의 버퍼:
네트워크 ─────→ [재생 버퍼] ─────→ 화면 (5-10초분)
동작:1. 초기 버퍼링: 5초분 데이터 다운로드2. 재생 시작3. 재생하면서 계속 버퍼 채우기4. 네트워크 느려지면: 버퍼의 데이터로 계속 재생5. 버퍼 소진: "버퍼링 중..." 표시버퍼가 없다면 네트워크 속도가 조금만 느려져도 재생이 끊기게 됩니다.
출처: Medium - Video Streaming Buffering Strategies, Netflix Tech Blog - Per-Title Encode Optimization
3. 데이터베이스 Batch Insert
// 버퍼 없이 개별 INSERT (느림)for (User user : users) { jdbcTemplate.update("INSERT INTO users VALUES (?, ?)", user.getId(), user.getName());}// 1,000개 INSERT → 10초
// 버퍼를 사용한 Batch INSERT (빠름)jdbcTemplate.batchUpdate( "INSERT INTO users VALUES (?, ?)", new BatchPreparedStatementSetter() { public void setValues(PreparedStatement ps, int i) { ps.setString(1, users.get(i).getId()); ps.setString(2, users.get(i).getName()); } public int getBatchSize() { return users.size(); } });// 1,000개 INSERT → 1초개별 쿼리를 버퍼에 모았다가 한 번에 전송하면 네트워크 왕복 시간을 줄일 수 있어요.
출처: Spring Framework Documentation - Batch Operations, Baeldung - Batch Insert/Update with Hibernate
9. 기술 업계 실전 사례
우리가 겪은 문제는 이미 다른 회사들도 겪었던 문제예요. 어떻게 해결했는지 살펴보겠습니다.
1. 카카오톡의 캐시 서버 진화 - 물리 서버 256대를 어떻게 줄였나
카카오톡은 초당 400만 건의 데이터 접근 요청을 처리해요. 처음에는 Memcached 물리 서버 256대를 운영했는데, 문제가 있었어요. 데이터는 적은데 트래픽 분산을 위해 노드를 늘리다 보니, 캐시 클러스터가 60개 노드인데 각 노드가 32GB 중 겨우 300MB만 쓰는 상황이 됐거든요.
게다가 물리 서버라 장애가 나면 대응이 느렸습니다. 개발자가 로그 알림으로 장애를 감지하고, 클라이언트 설정에서 해당 노드를 제거하고, 인프라팀에 새 장비를 요청하고, 새 노드를 설정에 추가하는 과정을 수동으로 했어요.
이걸 Redis + Kubernetes로 바꿨습니다. Sentinel로 자동 Failover를 구성하고, 내부 도구(Ban)를 만들어서 전체 과정을 자동화했어요. 이제는 노드 장애가 나도 자동으로 감지되고 복구돼요.
2. Cache Stampede 문제와 해결법
인기 있는 데이터의 캐시가 만료되는 순간, 수천 개의 요청이 동시에 DB를 조회하는 현상이에요. 캐시가 만료되는 시각 T에 요청 1000개가 동시에 들어오면, 모두 캐시 미스가 나고, 1000개가 전부 DB를 조회합니다. DB가 감당을 못 하고 죽어버려요.
해결 방법 1: 분산 락
첫 번째 요청만 DB를 조회하고 나머지는 대기시킵니다.
suspend fun getWithLock(key: String): String? { val cached = redis.opsForValue().get(key) if (cached != null) return cached
val lockKey = "lock:$key" val acquired = redis.opsForValue().setIfAbsent(lockKey, "1", 5, TimeUnit.SECONDS)
if (acquired == true) { try { val data = database.query(...) redis.opsForValue().set(key, data, 1, TimeUnit.HOURS) return data } finally { redis.delete(lockKey) } } else { delay(100) return redis.opsForValue().get(key) }}해결 방법 2: PER(Probabilistic Early Recomputation) 알고리즘
TTL이 얼마 안 남았으면 미리 백그라운드에서 갱신해요. 2015년 VLDB 컨퍼런스에 발표된 방법입니다.
fun getWithEarlyExpiration(key: String): String { val cached = redis.opsForValue().get(key) val ttl = redis.getExpire(key, TimeUnit.SECONDS)
if (ttl < TTL * 0.1) { CoroutineScope(Dispatchers.IO).launch { val fresh = database.query(...) redis.opsForValue().set(key, fresh, TTL, TimeUnit.SECONDS) } }
return cached ?: database.query(...)}DB 동시 쿼리가 1000회에서 1회로 줄어들어요. DB CPU 사용률도 90%에서 10%로 떨어집니다.
3. 조회수 같은 Write-Heavy 데이터 처리
유튜브 같은 서비스에서 동영상 조회수를 매번 DB에 쓰면 어떻게 될까요? 조회 1만 건/초면 MySQL UPDATE도 1만 건/초예요. DB가 버틸 수 없습니다.
Redis에 먼저 쓰고, 1분마다 배치로 MySQL에 동기화하는 방식으로 해결해요.
// 조회수 증가: Redis에만 (빠름)fun incrementViewCount(videoId: Long) { redis.opsForValue().increment("view:$videoId")}
// 1분마다 MySQL에 동기화@Scheduled(fixedRate = 60000)fun syncViewCounts() { val keys = redis.keys("view:*") val counts = redis.opsForValue().multiGet(keys)
jdbcTemplate.batchUpdate( "UPDATE videos SET view_count = view_count + ? WHERE video_id = ?", // ... batch update )
redis.delete(keys)}DB Write가 10,000건/초에서 100건/분으로 줄어들어요. 6,000배 감소입니다.
4. 안정 해시로 캐시 서버 추가해도 안정적으로
캐시 서버가 3대에서 4대로 늘어나면 어떻게 될까요? 일반적인 해시 함수(hash(key) % server_count)를 쓰면 모든 키의 위치가 바뀌어요.
키 "user:1": hash = 12345 Before: 12345 % 3 = 0 (서버 0) After: 12345 % 4 = 1 (서버 1)모든 키가 재배치되니 캐시 미스율이 100%가 됩니다. DB에 갑자기 엄청난 트래픽이 몰려요.
안정 해시(Consistent Hashing)를 쓰면 서버를 추가해도 평균적으로 k/n개의 키만 재배치돼요. 서버 3대에서 4대로 늘어나면 25%만 재배치됩니다.
해시 링에 서버와 키를 배치하고, 키 위치에서 시계방향으로 가장 먼저 만나는 서버에 저장하는 방식이에요. 가상 노드(Virtual Node)를 150개 정도 만들어서 데이터가 고르게 분산되도록 합니다.
아마존 DynamoDB, 카산드라, 디스코드 채팅 등에서 이 방식을 쓰고 있어요.
5. 토스의 웹 캐싱 전략
토스 프론트엔드 팀은 웹 성능을 높이기 위해 HTTP 캐시를 적극적으로 활용해요.
HTML 파일:
Cache-Control: max-age=0, s-maxage=31536000브라우저는 항상 서버에 재검증을 요청하고(max-age=0), CDN은 1년 동안 캐싱합니다(s-maxage=31536000). 배포할 때마다 CDN Invalidation을 실행해서 CDN이 새 HTML을 받아오게 해요.
JS/CSS 파일:
빌드할 때마다 URL에 버전 번호를 붙여서 고유한 URL을 만들어요.
/v1234/main.js/v1235/main.js이런 파일은 절대 바뀌지 않으니 max-age를 최대치로 설정합니다.
Cache-Control: max-age=31536000HTTP 캐시를 효율적으로 관리하려면 Cache-Control 헤더를 섬세하게 조절해야 한다는 게 토스 팀의 노하우예요.
10. 정리
캐시와 버퍼는 모두 임시 저장 공간이지만, 목적과 사용 방식이 달라요.
캐시 (Cache):
- 목적: 느린 메모리 접근을 줄여 속도 향상
- 특징: 원본 데이터의 복사본, 읽기 최적화, 투명한 동작
- 예시: CPU 캐시, 브라우저 캐시, Redis, CDN
- 핵심 원리: 지역성 (Temporal & Spatial Locality)
버퍼 (Buffer):
- 목적: 속도 차이가 있는 장치 사이의 데이터 이동 조절
- 특징: 이동 중인 데이터, 쓰기 최적화, 명시적 관리
- 예시: 키보드 버퍼, 디스크 버퍼, 네트워크 버퍼, 스트리밍 버퍼
- 핵심 원리: Producer-Consumer 패턴
두 개념을 정확히 이해하면 시스템 성능을 최적화하고, 면접에서도 명확하게 설명할 수 있습니다.
11. 참고 자료
공식 문서 및 표준
- Wikipedia - Memory Hierarchy - 메모리 계층 구조
- Wikipedia - CPU Cache - CPU 캐시 동작 원리
- Wikipedia - Circular Buffer - 링 버퍼 자료구조
- Wikipedia - Buffer Overflow - 버퍼 오버플로우 보안
- Linux man pages - free(1) - Linux 메모리 관리
- Java Documentation - BufferedWriter - Java 버퍼 I/O
- Oracle - Java Concurrency Utilities - Java 동시성
기술 자료
- GeeksforGeeks - Cache Memory in Computer Organization - 캐시 메모리 기초
- GeeksforGeeks - Difference between Cache and Buffer - 캐시 vs 버퍼
- GeeksforGeeks - Write Through and Write Back in Cache - 캐시 쓰기 정책
- GeeksforGeeks - Producer Consumer Problem in Java - 생산자-소비자 패턴
- GeeksforGeeks - What is Buffer in Computer Science? - 버퍼 개념
- Carnegie Mellon University - Introduction to Computer Systems - 15-213 강의 (캐시 메모리, 지역성 원리)
웹 개발 및 최적화
- MDN Web Docs - HTTP Caching - HTTP 캐싱
- web.dev - HTTP Caching - 웹 캐싱 최적화
- AWS - Caching Strategies with Redis - Redis 캐싱 패턴
- Baeldung - Spring Boot Redis Cache - Spring Redis 캐싱
- Cloudflare - What is a CDN? - CDN 개념
- AWS - What is a CDN? - CDN 활용
성능 최적화
- Spring Framework Documentation - Batch Operations - Spring 배치 처리
- Baeldung - Batch Insert/Update with Hibernate - Hibernate 배치
- Oracle - Java Performance Tuning Guide - Java 성능 튜닝
- Baeldung - Java BufferedWriter - BufferedWriter 활용
- Red Hat - Understanding Memory Usage on Linux - Linux 메모리
네트워크 및 스트리밍
- GeeksforGeeks - Socket Programming in Java - 네트워크 버퍼
- Medium - Video Streaming Buffering Strategies - 스트리밍 버퍼
- Netflix Tech Blog - Per-Title Encode Optimization - Netflix 최적화
보안
- OWASP - Buffer Overflow - 버퍼 오버플로우 대응
- Stack Overflow - What is the difference between buffer and cache? - 캐시 vs 버퍼 논의
한글 자료
- 나무위키 - 캐시 메모리 - 캐시 메모리 개념
한국 기술 블로그
- Naver D2 - 안정 해시 알고리즘 - 캐시 서버 분산 전략
- Naver D2 - Redis Cluster 도입 - 대용량 캐시 아키텍처
- 우아한기술블로그 - 실시간 댓글 개발기 (Redis Pub/Sub) - Redis 캐시와 Pub/Sub 활용
- 우아한기술블로그 - Cache Stampede 현상 해결 - 캐시 동시성 문제와 해결
- 카카오 기술블로그 - 카카오톡 캐싱 시스템의 진화 - 대규모 캐싱 시스템 설계
- LINE Engineering - Redis 성능 튜닝 - Redis 클러스터 최적화
- 토스 기술블로그 - 토스 코어 캐싱 전략 - 웹 서비스 캐시 전략
- 당근마켓 - 대용량 트래픽 대응 아키텍처 - CDN과 캐싱 레이어
1. Introduction
In computer systems, both “Cache” and “Buffer” are memory spaces that temporarily store data. However, their purposes and behaviors are fundamentally different. A cache stores frequently used data in fast memory for speed improvement, while a buffer temporarily holds data between a producer and consumer to regulate speed differences.
These two concepts are frequently covered in CS interviews and are essential for performance optimization and system design in practice.
Source: GeeksforGeeks - Difference between Cache and Buffer
2. Memory Hierarchy
Computer memory is designed in a hierarchical structure. The closer to the CPU, the faster but smaller and more expensive; the farther, the slower but larger and cheaper.

Caches exist in this hierarchy to bridge the speed gap between upper and lower layers. By copying frequently accessed data to faster memory, the number of accesses to slower memory can be reduced.
Source: Wikipedia - “Memory Hierarchy”
3. What Is Cache Memory?
Purpose of Cache
A cache is high-speed memory designed to improve data access speed. It takes a relatively long time for the CPU to read data from main memory (RAM). If frequently used data is copied closer to the CPU, it can be accessed much faster.
Key characteristics:
- Stores a copy of the original data
- Primarily improves read performance
- Even if data is missing, it can be fetched again from the original source
- Operates transparently (applications are unaware of it)
Source: Namu Wiki - “Cache Memory”
Cache Levels (L1, L2, L3)
Modern CPUs have multiple levels of cache.
L1 Cache (Level 1 Cache)
The cache closest to the CPU core, split into an instruction cache (I-Cache) and a data cache (D-Cache).
L1 cache structure:

By following the Harvard Architecture to separate instructions and data, the CPU can simultaneously read instructions and process data.
Source: Wikipedia - CPU Cache, GeeksforGeeks - Cache Memory in Computer Organization
L2 Cache (Level 2 Cache)
Larger but slightly slower than the L1 cache. Typically exists independently for each CPU core.

Source: Intel - OpenCL Memory Hierarchy
L3 Cache (Level 3 Cache)
A cache shared by all CPU cores. The largest, but slower than L1 and L2.

Source: GeeksforGeeks - “Cache Memory in Computer Organization”
Principle of Locality
The reason caches work effectively is that programs exhibit locality.
Temporal Locality
Data that was recently accessed is likely to be accessed again in the near future.
// Temporal locality exampleint sum = 0;for (int i = 0; i < 1000; i++) { sum += array[i]; // The variable sum is accessed repeatedly}The variable sum is continuously reused throughout the loop, so storing it in cache is efficient.
Source: Carnegie Mellon University - Introduction to Computer Systems (15-213 lecture materials)
Spatial Locality
Data near recently accessed data is likely to be accessed soon.
// Spatial locality exampleint[] array = new int[1000];for (int i = 0; i < 1000; i++) { sum += array[i]; // Accessing consecutive memory addresses}Since arrays are stored contiguously in memory, when array[0] is loaded into cache, nearby elements like array[1] and array[2] are also fetched together (in cache line units).
Cache Line: Caches fetch data not in individual bytes but in block units. Typically, 64-byte cache lines are used.

Source: Wikipedia - CPU Cache, GeeksforGeeks - Locality of Reference
4. What Is a Buffer?
Purpose of Buffers
A buffer is a space that temporarily stores data between two devices with different speeds. When the speed at which a producer generates data differs from the speed at which a consumer processes it, the buffer absorbs that difference.
Key characteristics:
- Manages data movement (not copying)
- Primarily improves write performance
- Prevents data loss
- Managed explicitly (applications are aware of it)
Types of Buffers
Keyboard Buffer
Bridges the gap between the speed at which users press keys and the speed at which programs process input.

User input: H -> e -> l -> l -> o [Enter] ↓Keyboard buffer: [H][e][l][l][o][\n] ↓Program: reads "Hello\n"Without a keyboard buffer, keystrokes could be lost before the program reads the input.
Disk Buffer
Disk is much slower than RAM. If the program waits every time data is written to disk, it stalls.
// Writing to disk without bufferfor (int i = 0; i < 1000; i++) { disk.write(data[i]); // Disk access every time (very slow)}
// Writing to disk with bufferBufferedWriter writer = new BufferedWriter(new FileWriter("file.txt"));for (int i = 0; i < 1000; i++) { writer.write(data[i]); // Write to buffer (fast)}writer.flush(); // Flush buffer contents to disk at onceUsing a buffer allows multiple small writes to be aggregated into a single large write.
Source: Java Documentation - BufferedWriter, GeeksforGeeks - BufferedWriter in Java
Network Buffer
Network communication uses send buffers and receive buffers.

The receive buffer absorbs the speed difference between data arriving from the network and the application reading the data.
// TCP socket buffer size configurationSocket socket = new Socket();socket.setSendBufferSize(65536); // Send buffer: 64KBsocket.setReceiveBufferSize(65536); // Receive buffer: 64KBSource: Java Documentation - Socket, GeeksforGeeks - Socket Programming in Java
Ring Buffer (Circular Buffer)
A data structure that uses a fixed-size buffer in a circular fashion.

Since the pointer wraps around to the beginning when it reaches the end, memory can be reused.
typedef struct { char buffer[BUFFER_SIZE]; int read_pos; int write_pos; int count; // Number of currently stored data items} RingBuffer;
void ring_buffer_write(RingBuffer* rb, char data) { if (rb->count < BUFFER_SIZE) { rb->buffer[rb->write_pos] = data; rb->write_pos = (rb->write_pos + 1) % BUFFER_SIZE; rb->count++; }}
char ring_buffer_read(RingBuffer* rb) { if (rb->count > 0) { char data = rb->buffer[rb->read_pos]; rb->read_pos = (rb->read_pos + 1) % BUFFER_SIZE; rb->count--; return data; } return -1; // Buffer is empty}Source: Wikipedia - Circular Buffer, GeeksforGeeks - Circular Queue
5. Cache vs Buffer: Key Differences
| Comparison | Cache | Buffer |
|---|---|---|
| Purpose | Speed improvement (reduce slow memory accesses) | Speed difference regulation (producer-consumer synchronization) |
| Data nature | Copy of original data | Data in transit |
| Data lifespan | Can be invalidated when original changes | Consumed once read (one-time use) |
| Size | Relatively small (capacity-constrained) | Relatively flexible |
| Management | Hardware/system (automatic) | Software (explicit) |
| Primary operation | Read optimization | Write optimization |
| Transparency | Transparent (applications are unaware) | Explicit (applications manage it) |
| Examples | CPU cache, browser cache, DNS cache | Keyboard buffer, disk buffer, network buffer |
Source: Stack Overflow - What is the difference between buffer and cache?, GeeksforGeeks - Difference between Cache and Buffer
Differences from a Memory Perspective
Running the Linux free command shows cache and buffer displayed separately.
$ free -h total used free shared buff/cache availableMem: 15Gi 8.0Gi 2.0Gi 1.0Gi 5.0Gi 6.0GiSwap: 2.0Gi 0B 2.0Gi- buff: Block device metadata buffer (filesystem metadata)
- cache: Page cache (file contents)
File reading process:

Source: Linux man pages - free(1), Red Hat - Understanding Memory Usage on Linux
6. How Caches Work
Write-Through vs Write-Back
There are two policies for writing data to cache.
Write-Through (Immediate Write)
Data is written to both the cache and main memory simultaneously.

Pros:
- Maintains data consistency (cache and memory are always identical)
- Simple implementation
Cons:
- Writes are slow (memory access every time)
- Write performance degradation
Source: GeeksforGeeks - Write Through and Write Back in Cache, Wikipedia - Cache (computing)
Write-Back (Deferred Write)
Data is written only to the cache, and written to memory later when the cache line is evicted.

Pros:
- Writes are fast (writing to cache completes the operation)
- Multiple writes can be batched into one
Cons:
- Data inconsistency possible (cache and memory differ)
- Dirty bit management required
Source: GeeksforGeeks - Write Through and Write Back in Cache, Carnegie Mellon University - Cache Memories
Dirty Bit
The dirty bit is a flag indicating whether a cache line has been modified.
Cache line structure:
Operation flow:
- When a cache line is loaded from memory:
Dirty Bit = 0(Clean) - When the CPU writes to the cache line:
Dirty Bit = 1(Dirty) - When the cache line is evicted:
Dirty Bit = 0: Simply evict (identical to memory)Dirty Bit = 1: Write back to memory, then evict (Write-Back)
7. How Buffers Work
Producer-Consumer Pattern
Buffers are typically used in the producer-consumer problem.
// Shared bufferclass SharedBuffer { private Queue<Integer> buffer = new LinkedList<>(); private int capacity;
public SharedBuffer(int capacity) { this.capacity = capacity; }
// Producer: generates data public synchronized void produce(int data) throws InterruptedException { while (buffer.size() == capacity) { wait(); // Wait if buffer is full } buffer.add(data); System.out.println("Produced: " + data); notifyAll(); // Wake up consumers }
// Consumer: consumes data public synchronized int consume() throws InterruptedException { while (buffer.isEmpty()) { wait(); // Wait if buffer is empty } int data = buffer.poll(); System.out.println("Consumed: " + data); notifyAll(); // Wake up producers return data; }}
// Producer threadclass Producer extends Thread { private SharedBuffer buffer;
public void run() { for (int i = 0; i < 10; i++) { try { buffer.produce(i); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }}
// Consumer threadclass Consumer extends Thread { private SharedBuffer buffer;
public void run() { for (int i = 0; i < 10; i++) { try { buffer.consume(); Thread.sleep(200); // Slower than producer } catch (InterruptedException e) { e.printStackTrace(); } } }}Operation flow:

Even when the producer is faster than the consumer, the buffer holds data in between so nothing is lost.
Source: GeeksforGeeks - Producer Consumer Problem in Java, Oracle - Java Concurrency Utilities
Buffer Overflow
When data is written beyond the buffer’s size, a buffer overflow occurs.
char buffer[10];strcpy(buffer, "This is a very long string"); // Buffer overflow!
// Data beyond buffer[10] overwrites adjacent memoryMemory structure:

Buffer overflow is a serious security vulnerability that allows attackers to manipulate return addresses and execute malicious code.
Defense techniques:
// Safe method 1: Size limitingstrncpy(buffer, input, sizeof(buffer) - 1);buffer[sizeof(buffer) - 1] = '\0';
// Safe method 2: Bounds checkingif (strlen(input) < sizeof(buffer)) { strcpy(buffer, input);}
// Safe method 3: Using safe functionsstrlcpy(buffer, input, sizeof(buffer)); // BSD systemsSource: Wikipedia - Buffer Overflow, OWASP - Buffer Overflow
8. Practical Use Cases
Cache Use Cases
1. Web Browser Cache

2. Redis Cache
// Check cache before querying the databasepublic User getUserById(String userId) { // 1. Check cache User user = redisTemplate.opsForValue().get("user:" + userId);
if (user != null) { return user; // Cache hit }
// 2. Cache miss: Query DB user = userRepository.findById(userId);
// 3. Store in cache (TTL: 1 hour) redisTemplate.opsForValue().set("user:" + userId, user, 1, TimeUnit.HOURS);
return user;}Performance improvement:
- Cache hit: 1-5ms
- DB query: 50-200ms
- 10x or more performance improvement
Source: Redis Documentation - Caching Patterns, Baeldung - Spring Cache with Redis
3. CDN (Content Delivery Network)
User (Korea) -> Origin server (USA) - Latency: 200ms - Bandwidth cost: High
After CDN deployment:User (Korea) -> CDN edge server (Seoul) - Latency: 10ms - Bandwidth cost: Low - Origin server load reducedBuffer Use Cases
1. Log Buffering
// Writing logs without buffer (slow)public void logWithoutBuffer(String message) { fileWriter.write(message + "\n"); fileWriter.flush(); // Disk write every time}
// Writing logs with buffer (fast)public class BufferedLogger { private BufferedWriter writer; private int bufferSize = 8192; // 8KB buffer
public void log(String message) throws IOException { writer.write(message + "\n"); // Automatically flushes when buffer is full }
public void close() throws IOException { writer.flush(); // Flush remaining data to disk writer.close(); }}Performance comparison:
- Without buffer: 10,000 logs -> 5 seconds
- With buffer: 10,000 logs -> 0.5 seconds
- 10x performance improvement
Source: Oracle - Java Performance Tuning Guide, Baeldung - Java BufferedWriter
2. Video Streaming Buffer
Video player buffer:
Network -------> [Playback buffer] -------> Screen (5-10 seconds worth)
Operation:1. Initial buffering: Download 5 seconds of data2. Start playback3. Continue filling buffer while playing4. When network slows: Continue playback using buffered data5. Buffer exhausted: Display "Buffering..."Without a buffer, even a slight network slowdown would cause playback interruptions.
Source: Medium - Video Streaming Buffering Strategies, Netflix Tech Blog - Per-Title Encode Optimization
3. Database Batch Insert
// Individual INSERTs without buffer (slow)for (User user : users) { jdbcTemplate.update("INSERT INTO users VALUES (?, ?)", user.getId(), user.getName());}// 1,000 INSERTs -> 10 seconds
// Batch INSERT with buffer (fast)jdbcTemplate.batchUpdate( "INSERT INTO users VALUES (?, ?)", new BatchPreparedStatementSetter() { public void setValues(PreparedStatement ps, int i) { ps.setString(1, users.get(i).getId()); ps.setString(2, users.get(i).getName()); } public int getBatchSize() { return users.size(); } });// 1,000 INSERTs -> 1 secondBy accumulating individual queries in a buffer and sending them all at once, network round-trip time can be reduced.
Source: Spring Framework Documentation - Batch Operations, Baeldung - Batch Insert/Update with Hibernate
9. Real-World Industry Case Studies
The problems we face have already been encountered by other companies. Let us look at how they solved them.
1. KakaoTalk’s Cache Server Evolution - How They Reduced 256 Physical Servers
KakaoTalk handles 4 million data access requests per second. Initially, they operated 256 physical Memcached servers, but there was a problem. Despite having little data, they kept adding nodes for traffic distribution, resulting in a situation where a 60-node cache cluster had each node using only 300MB out of 32GB.
Furthermore, since these were physical servers, incident response was slow. Developers had to manually detect failures from log alerts, remove the affected node from client configuration, request new hardware from the infrastructure team, and add the new node to the configuration.
They migrated to Redis + Kubernetes. They configured automatic failover with Sentinel and built an internal tool (Ban) to automate the entire process. Now node failures are automatically detected and recovered.
Source: Kakao Tech Blog - if(kakao)2020 KakaoTalk Caching System Evolution
2. The Cache Stampede Problem and Solutions
This is a phenomenon where thousands of requests simultaneously query the DB the moment a popular data item’s cache expires. At time T when the cache expires, if 1,000 requests arrive simultaneously, they all experience a cache miss and all 1,000 query the DB. The DB cannot handle it and goes down.
Solution 1: Distributed Lock
Only the first request queries the DB; the rest wait.
suspend fun getWithLock(key: String): String? { val cached = redis.opsForValue().get(key) if (cached != null) return cached
val lockKey = "lock:$key" val acquired = redis.opsForValue().setIfAbsent(lockKey, "1", 5, TimeUnit.SECONDS)
if (acquired == true) { try { val data = database.query(...) redis.opsForValue().set(key, data, 1, TimeUnit.HOURS) return data } finally { redis.delete(lockKey) } } else { delay(100) return redis.opsForValue().get(key) }}Solution 2: PER (Probabilistic Early Recomputation) Algorithm
If the TTL is running low, proactively refresh in the background. This method was presented at the VLDB Conference in 2015.
fun getWithEarlyExpiration(key: String): String { val cached = redis.opsForValue().get(key) val ttl = redis.getExpire(key, TimeUnit.SECONDS)
if (ttl < TTL * 0.1) { CoroutineScope(Dispatchers.IO).launch { val fresh = database.query(...) redis.opsForValue().set(key, fresh, TTL, TimeUnit.SECONDS) } }
return cached ?: database.query(...)}Concurrent DB queries drop from 1,000 to 1. DB CPU usage also falls from 90% to 10%.
3. Handling Write-Heavy Data Like View Counts
What happens if a service like YouTube writes video view counts to the DB every time? At 10,000 views per second, that means 10,000 MySQL UPDATEs per second. The DB cannot sustain this.
The solution is to write to Redis first, then synchronize to MySQL in batches every minute.
// Increment view count: Redis only (fast)fun incrementViewCount(videoId: Long) { redis.opsForValue().increment("view:$videoId")}
// Sync to MySQL every minute@Scheduled(fixedRate = 60000)fun syncViewCounts() { val keys = redis.keys("view:*") val counts = redis.opsForValue().multiGet(keys)
jdbcTemplate.batchUpdate( "UPDATE videos SET view_count = view_count + ? WHERE video_id = ?", // ... batch update )
redis.delete(keys)}DB writes drop from 10,000/sec to 100/min — a 6,000x reduction.
4. Stable Cache Server Scaling with Consistent Hashing
What happens when cache servers grow from 3 to 4? With a typical hash function (hash(key) % server_count), the location of every key changes.
Key "user:1": hash = 12345 Before: 12345 % 3 = 0 (Server 0) After: 12345 % 4 = 1 (Server 1)All keys are redistributed, causing a 100% cache miss rate. The DB suddenly gets hit with massive traffic.
With Consistent Hashing, when a server is added, only an average of k/n keys are redistributed. Going from 3 to 4 servers means only 25% redistribution.
Servers and keys are placed on a hash ring, and each key is stored on the first server encountered clockwise from the key’s position. Around 150 virtual nodes are created to ensure even data distribution.
Amazon DynamoDB, Cassandra, and Discord chat all use this approach.
5. Toss’s Web Caching Strategy
Toss’s frontend team aggressively leverages HTTP caching to improve web performance.
HTML files:
Cache-Control: max-age=0, s-maxage=31536000The browser always sends a revalidation request to the server (max-age=0), while the CDN caches for 1 year (s-maxage=31536000). CDN Invalidation is executed with every deployment so the CDN fetches the new HTML.
JS/CSS files:
A version number is appended to the URL with every build to create unique URLs.
/v1234/main.js/v1235/main.jsSince these files never change, max-age is set to the maximum.
Cache-Control: max-age=31536000The Toss team’s know-how is that managing HTTP cache effectively requires fine-tuning Cache-Control headers.
10. Summary
Both caches and buffers are temporary storage spaces, but their purposes and usage differ.
Cache:
- Purpose: Speed improvement by reducing slow memory accesses
- Characteristics: Copy of original data, read optimization, transparent operation
- Examples: CPU cache, browser cache, Redis, CDN
- Core principle: Locality (Temporal & Spatial)
Buffer:
- Purpose: Regulating data movement between devices with different speeds
- Characteristics: Data in transit, write optimization, explicit management
- Examples: Keyboard buffer, disk buffer, network buffer, streaming buffer
- Core principle: Producer-Consumer pattern
Understanding both concepts accurately enables system performance optimization and clear explanations in interviews.
11. References
Official Documentation and Standards
- Wikipedia - Memory Hierarchy - Memory hierarchy structure
- Wikipedia - CPU Cache - CPU cache mechanics
- Wikipedia - Circular Buffer - Ring buffer data structure
- Wikipedia - Buffer Overflow - Buffer overflow security
- Linux man pages - free(1) - Linux memory management
- Java Documentation - BufferedWriter - Java buffered I/O
- Oracle - Java Concurrency Utilities - Java concurrency
Technical Resources
- GeeksforGeeks - Cache Memory in Computer Organization - Cache memory fundamentals
- GeeksforGeeks - Difference between Cache and Buffer - Cache vs buffer
- GeeksforGeeks - Write Through and Write Back in Cache - Cache write policies
- GeeksforGeeks - Producer Consumer Problem in Java - Producer-consumer pattern
- GeeksforGeeks - What is Buffer in Computer Science? - Buffer concepts
- Carnegie Mellon University - Introduction to Computer Systems - 15-213 lectures (cache memory, locality)
Web Development and Optimization
- MDN Web Docs - HTTP Caching - HTTP caching
- web.dev - HTTP Caching - Web caching optimization
- AWS - Caching Strategies with Redis - Redis caching patterns
- Baeldung - Spring Boot Redis Cache - Spring Redis caching
- Cloudflare - What is a CDN? - CDN concepts
- AWS - What is a CDN? - CDN usage
Performance Optimization
- Spring Framework Documentation - Batch Operations - Spring batch processing
- Baeldung - Batch Insert/Update with Hibernate - Hibernate batch
- Oracle - Java Performance Tuning Guide - Java performance tuning
- Baeldung - Java BufferedWriter - BufferedWriter usage
- Red Hat - Understanding Memory Usage on Linux - Linux memory
Networking and Streaming
- GeeksforGeeks - Socket Programming in Java - Network buffer
- Medium - Video Streaming Buffering Strategies - Streaming buffer
- Netflix Tech Blog - Per-Title Encode Optimization - Netflix optimization
Security
- OWASP - Buffer Overflow - Buffer overflow defense
- Stack Overflow - What is the difference between buffer and cache? - Cache vs buffer discussion
Korean Resources
- Namu Wiki - Cache Memory - Cache memory concepts
Korean Tech Blogs
- Naver D2 - Consistent Hashing Algorithm - Cache server distribution strategy
- Naver D2 - Redis Cluster Adoption - Large-scale cache architecture
- Woowahan Tech Blog - Real-time Comments Development (Redis Pub/Sub) - Redis cache and Pub/Sub usage
- Woowahan Tech Blog - Solving Cache Stampede - Cache concurrency issues and solutions
- Kakao Tech Blog - KakaoTalk Caching System Evolution - Large-scale caching system design
- LINE Engineering - Redis Performance Tuning - Redis cluster optimization
- Toss Tech Blog - Toss Core Caching Strategy - Web service cache strategy
- Karrot - High-Traffic Architecture - CDN and caching layers