이 글은 특정 구현에 종속되는 내용을 제외한 이론 위주의 정리 글이며, Look-Aside, Write-Back 패턴 등 Cache Strategy에 대한 내용들은 “Redis : Cache Strategy Pattern” 에서 다루었기 때문에 제외하였습니다.

 

충분히 잘못된 내용이 있을 수 있습니다. 그러한 것들이 있다면 댓글과 채널톡을 통해 피드백 부탁드립니다.

 

 

 

Cache가 필요한 이유?

서비스에서 자주 조회되는 데이터에 대해서 매번 접근하고 조합한 뒤 모델을 만들어 응답하는 것은 생각보다 많은 리소스를 사용하게 합니다. (Disk I/O, Join, Calculate, Sort 등..) 데이터 정규화 수준이 높거나 볼륨이 크고 서비스가 구현된 방식이 동기식이라고 가정한다면, 1개의 Request 당 최소한 1개의 Thread를 사용하기 때문에 연산의 지연 시간에 따라 사용자의 요청 수를 처리량이 따라가지 못해 처리되지 못하는 요청들이 생길 수 있습니다.

SNS의 Timeline, Game Ranking List, Portal Site의 Main Page 등

 

이때 우리는 제공할 데이터 모델을 만들거나 접근한 다음 이 정보를 특정 시간 동안 보관하면서 바로 이용하거나 응답하도록 하고 서비스가 사용하는 리소스 (연산, I/O)를 최소화할 방법을 찾아야 하는데 이것이 바로 Cache입니다.

 

 

 

 

Cache?

Cache란 앞서 설명한 것처럼 반복되는 데이터에 대한 접근 및 연산 비용을 줄이기 위해 좀 더 빠르게 접근 가능한 영역에 완성된 데이터 모델을 보관하는 것이라고 볼 수 있습니다.

 

대표적으로 사용되는 Cache의 Use Case로는 OS Level의 3 Level Cache와 Web Cache가 있습니다.

 

3 Level Cache는 L1 ~ L3 Cache가 계층적인 구조로 배치되어 있는 형태를 말하며, JVM과 같은 VM의 Memory Model 설계에서 동작하는 Thread들이 Memory 영역에 있는 데이터를 Capturing 하여 구성되어 있는 3 Level Cache에 저장하고 데이터 전송으로 발생하는 CPU의 Idle을 최소화해 전체적인 연산 성능을 향상 시킵니다.

이것은 Thread 간의 Visibility 문제가 발생하는 근본 원인이기도 합니다.

 

Web Cache는 Reverse Proxy이나 CDN과 Browser의 Local Cache 등 다양한 요소를 통해 제공되는 Cache이며, HTML Document나 Static Resource (Image, Video 등) 제공 시 실제 서버와의 물리적인 거리로 인한 응답 지연 이슈, 물리 서버가 존재하는 또는 경로에 해당하는 네트워크의 대역폭에 의한 bottleneck, 갑작스럽게 발생하는 Flash Crowds를 대처하기 위해 사용되고 있습니다.

 

이 글에서는 Application Cache에 대한 개념을 정리합니다.

 

 

 

 

Cache design

Cache의 효용성을 뒷받침하는 원칙으로는 전체 결과의 80%가 원인의 20%에 의해 일어나는 현상을 가리키는 Pareto principle이 있습니다.

 

이 원칙을 기반으로 Cache에 저장할 데이터를 선택할 때 모든 데이터를 보관하는 것보단 20%에 해당하는 데이터 모델을 판단하는 것을 기본으로 합니다.

Cache가 효율적으로 동작하려면 Hit rate를 극대화시켜야 하는데 만약 모든 데이터가 골고루 사용되는 서비스에 억지로 작은 Size의 Cache를 도입하려고 한다면, Cache를 사용하지 못하고 실제 데이터를 가진 곳을 접근하고 Cache를 추가하는 Cache Miss가 빈번하게 일어날 수 있습니다.

 

 

Cache의 대상이 되는 데이터는 Request Pattern이 Read에 집중되고 Write가 적거나 없는 또는 결정적인 특성을 가지는 데이터여야 합니다. 만약 Write가 자주 발생하는 데이터를 Caching 한다면, 최신 데이터 보장을 위해 write가 있을 때마다 Cache를 Evition 하고 조회 시 다시 Cache를 등록하는 Overhead가 지속적으로 발생하게 됩니다.

이러한 경우에는 Cache를 활용하지 않거나 실제 응답 결과가 아닌 사용되는 데이터를 Caching 하여야 합니다. ex) id

 

 

Cache에 보관할 데이터 모델을 판단하는 기준으로는 Data Locality principle을 따르는데, 이는 크게 Temporal locality와 Spatial locality로 구분할 수 있습니다.

 

Temporal locality란 요청이 접근한 특정 데이터는 가까운 시일 내에 또 접근할 가능이 높다는 원칙입니다. 이때 데이터는 Loop 등에서 활용되는 Condition 값이나 다른 데이터가 참조하는 Reference id 일 가능성이 높습니다.

loop count, branch condition, fk…

 

Spatial locality란 요청이 접근한 특정 데이터의 주변은 같이 접근할 가능성이 높다는 원칙입니다. 이때 데이터는 메모리 공간을 연속적으로 사용하는 Array 형태일 가능성이 높습니다.

iterate over array or array list (Dynamic array)…

 

 

Cache를 eviction 하는 방식으로는 대표적으로 Expire, LRU, LFU, FIFO 등이 있습니다.

 

Expire 방식은 단순하게 Cache를 생성하였을 때 Memory에 유지될 시간을 지정하는 방식입니다.

 

LRU 방식은 최근에 사용되지 않은지 오래된 Cache를 Timestamp 등을 통해 판단하여 우선적으로 제거하는 방식입니다.

 

LFU 방식은 가장 적게 사용된 Cache를 Cache Hit count 등을 통해 판단하여 우선적으로 제거하는 방식입니다. 이 방식은 앞서 언급한 Pareto principle 분포를 따르는 경우 더욱 효과적입니다.

 

FIFO 방식은 Cache가 저장된 순서대로 제거하는 방식입니다. 이 방식은 일정 시간이 지나면 접근하지 않을 데이터(ex Access Token과 같은 인증 정보) 등을 관리하는데 효과적인 수단이 될 수 있습니다.

 

 

 

 

Cache 종류?

Application에서 Cache를 적용하는 범위는 크게 Local, Global, Distributed Cache 등이 있습니다.

 

Local Cache란 Application마다 가지고 있는 In-Memory, Disk에 구성되어있는 방식을 의미합니다. Cache Abstraction, Caffeine, EhCache…

 

Application에서 바로 접근 가능하기 때문에 성능이 뛰어나지만 동일 Application이 Scale-out 된 경우에는 각 Local Cache가 동기화되지 않기 때문에 데이터의 정합성 문제가 발생하게 됩니다.

 

일반적으로 Local Cache 간의 데이터를 동기화하기 위해 Event를 발행하여 다른 Application에 변경된 사항을 전달하는 방식을 많이 사용하나 동기화 로직이 p2p 형태로 구현되어 있다면 전체 서비스 확장성에 영향을 줄 수 있습니다.

이는 데이터의 특징에 따라 expire를 짧게 두는 것으로 최소화할 수 있고 Message Queue나 Redis를 이용한 Pub/Sub Pattern을 사용할 수도 있지만, 결국 Eveuntual Consistency 문제가 발생하게 됩니다.

 

 

 

Global Cache란 네트워크로 접근 가능한 Cache 서버를 구성하고 Application들이 이를 접근하도록 하는 방식을 의미합니다. Redis Server, MemCached…

 

Local Cache에서 발생하는 데이터 정합성 문제를 해결하는 방법이지만 네트워크를 경유하여 데이터에 접근하기 때문에 Local Cache보다 성능이 낮으며 Application 단위로만 장애가 한정되는 Local Cache에 비해서 Global Cache는 SPoF가 되기 때문에 장애 발생 시 모든 Application에 장애가 전파될 수 있습니다.

이를 방지하기 위해 기본적으로 이중화 구성이 필요하며 약간의 Infra 구성 및 운영 비용이 발생하게 됩니다.

 

 

Distributed Cache란 여러 서버를 Cluster로 묶어 Memory, Disk에 데이터를 분산 저장시키고 서버들이 Hash와 같은 특정 Key를 통해 접근할 서버를 지정하는 방식을 의미합니다. Redis Cluster, Acrus, Hazelcast, Infinispan...

 

새로운 Cache 서버를 쉽게 추가할 수 있어 확장성이 매우 높고 Scale out의 이점을 통해 높은 처리량을 가질 수 있지만 상대적으로 높은 infra 구성 및 운영 비용이 발생하며, 특정 서버의 장애로 인한 일부 데이터 유실이 발생할 수 있고 Consistency Hashing을 사용하는 경우 서버의 요청 부하가 다음 서버로 전달되어 연쇄적인 장애가 발생할 수 있습니다.

 

 

 

 

Cache 종류?

 

 

+ Recent posts