끝없는 캐시의 피라미드
현대 시스템들은 캐시로 만들어져있다고 해도 과언이 아니다.
CPU 같은 하드웨어 레벨부터, 로우레벨 시스템, 네트워크 레이어, 하이레벨 애플리케이션에 이르기까지, 모든 프로그램들은 수많은 캐시 계층 위에서 동작한다.
캐시가 어떤 식으로 사방에 퍼져있는지 간략하게 정리해보겠다.
폰 노이만 아키텍쳐
우리가 쓰는 디스크-RAM-캐시-CPU의 컴퓨터 아키텍쳐를 폰 노이만 아키텍쳐라고 부른다.
사실 이러한 구조 자체도 일종의 캐시라고 볼 수 있다.
당연히 가장 사이즈가 크고 저렴한 것이 디스크이니, 디스크에 모든걸 다 저장해놓고 쓰면 편할 것이다.
하지만 성능과 비용 효율성의 문제로 중간중간 캐시 계층을 겹겹이 쌓은 구조가 되어버렸다.
어떻게 보면 RAM은 디스크에 대한 캐시이고, CPU 내 Cache 메모리는 RAM에 대한 캐시다.
CPU와 캐시
현대 CPU의 뛰어난 성능도 사실은 캐시에서 나오는 것이라고 봐도 무방하다.
메모리는 느리고, 레지스터는 너무 작기 때문에 중간 캐시 계층을 추가해서 적절한 타협접을 잡은 것이다.
https://blog.naver.com/sssang97/223143443182
https://blog.naver.com/sssang97/223422783855
그래서 프로그램을 최적화하려면 CPU의 캐시 동작 원리를 아는 것이 꽤 중요하다.
이런건 컴파일러들도 최적화를 못한다.
파일과 캐시
우리가 파일을 쓰고 읽는 동작 자체도 사실은 캐시 기반으로 동작한다.
파일을 쓴다고 해서 진짜 디스크에 바로 쓰는 것도 아니고, 파일을 읽는다고 해서 항상 디스크에서 곧장 읽어오는 것도 아니다.
OS가 내부적으로 메모리 기반의 캐시 계층을 추가해서, 디스크 접근을 최소화하려는 노력들을 뒤에서 해주기 때문이다. 그래서 일반적인 상황에서의 성능은 좋지만, 장애 내구성이 떨어진다는 tradeoff가 존재한다.
데이터베이스와 캐시
데이터베이스도 항상 내부적으로 캐시 매커니즘을 적용한다.
기본적으로는 디스크 기반으로 데이터 내구성을 보장하나, 디스크를 통해서만 읽기/쓰기를 처리하는건 너무 느리기 때문이다.
그래서 RDB, NoSQL을 가리지 않고 모든 데이터베이스들은 중간에 캐싱을 위한 메모리 계층을 둔다. 반복적으로 읽었거나 최근에 생성하거나 읽은 데이터를 메모리에 미리 올려놓고 사용하는 것이다.
이를 통해서 쓰기/읽기에 발생하는 디스크 액세스를 줄이고, 처리량을 극대화한다.
Elasticsearch와 캐시
데이터베이스들 중에서도 캐시를 탄력적으로 가장 잘 활용하는 것은 아마 Elasticsearch일 것이다.
이건 단순 최근 데이터나 데이터 일부를 캐싱하는 것이 아니라, 필터링 결과에 대한 값을 캐싱한다.
그래서 필터링 검색이 쌓이고 쌓일수록, 해당 필터 검색에 대한 캐시값이 누적되면서 더욱 안정적인 성능을 낼 수 있는 것이다. Elasticsearch의 놀라운 필터링 성능은 이런 적극적인 캐시 구조에서도 나온다.
https://blog.naver.com/sssang97/223664141145
서버와 인메모리 캐시
많은 서버 시스템들에서는 Redis로 대표되는 인메모리 캐시 레이어를 많이 사용한다.
반복적으로 발생하는 읽기 처리에 대한 값을 미리 캐싱해놨다가 꺼내서 레이턴시를 최적화하는 것이다.
값의 변화에 민감하지 않고, 약간의 딜레이를 둬도 좋다면 이 또한 매우 효과적인 대응책이 된다.
하지만 인메모리 특유의 낮은 비용효율성, 낮은 확장성 때문에 무작정 캐시에 우겨넣기보다는 정책을 잘 정하는 것이 중요하다.
대부분의 대규모 서비스들에서는 캐시 계층을 많이 적용해놔서 쓰다보면 실제로 유저 수준에서도 딜레이가 관측되는 경우가 많다.
도메인 캐시
도메인 주소로부터 IP를 가져오는 단순한 동작에도 사실 수많은 캐시 계층이 존재한다.
DNS 서버는 사실 여러개의 단계로 나뉘어있는데, 그 단계마다도 캐시가 존재할뿐더러, 최종 클라이언트의 로컬 환경에도 캐시를 쌓아서 쓰기 때문이다.
그래서 도메인 설정 값을 바꿀때는 즉시 반영되리라는 기대는 하지 않는 것이 좋다.
클라이언트 캐시까지 만료되려면 보통 며칠은 기다려야 한다.
브라우저와 캐시
우리가 사이트에 접속해서 이리저리 이동할때도, 사실 수많은 캐시 데이터들이 쌓이게 된다.
매번 모든 리소스를 가져오는 것은 당연히 매우 비효율적이기 때문이다.
https://blog.naver.com/sssang97/223708918277
그래서 이를 잘 고려해서, 최적화된 캐시 구조를 잘 잡는 것도 성능/처리량에 매우 주요하다.
CDN과 캐시
이미지를 사용자에게 제공하는 건, 사실 굉장히 비효율적이고 확장하기 어려운 기능이다.
이미지는 개별 사이즈가 제법 큰데다가, 한번에 여러 이미지를 로드해서 사용자마다 뿌려주는건 디스크 I/O, 메모리 자원, 네트워크 트래픽 전부를 엄청나게 갉아먹을 수 있기 때문이다.
그래서 정적인 이미지를 제공할 때는 CDN이라고 하는 특수화된 서버를 통해, 엣지마다 데이터를 캐싱해놓고 사용하는 경우가 많다.
이 CDN 자체가 독립적인 캐시 서버인 셈이다.
https://blog.naver.com/sssang97/223114800667
참고: 캐싱 전략
소프트웨어 수준에서 캐시를 구현하게 된다면, 캐시 적용 매커니즘을 정하는 것도 꽤 중요하다.
https://blog.naver.com/sssang97/223479959982