[Database] 캐시 압력(Cache Pressure)
본문 바로가기

ComputerScience/Database

[Database] 캐시 압력(Cache Pressure)

캐시 압력(Cache Pressure)

캐시 압력이란?

캐시 압력은 캐시 메모리가 부족해지면서 발생하는 문제를 말한다.

캐시가 가득 차서 더 이상 새로운 데이터를 저장할 수 없고, 이로 인해 기존 데이터를 삭제해야 하는 상황이 발생할 수 있다.

캐시 압력이 증가하면, 캐시 항목을 삭제하거나 새 데이터를 캐시에 저장하는 동안 대기 상태가 발생할 수 있다.

캐시 압력 예시

Spring에서 사용자별 페이징 데이터를 캐시에 저장하는 경우, 특히 대규모 사용자와 많은 데이터를 처리하는 시스템에서는 캐시 메모리가 빠르게 소진될 수 있으며, 이는 “캐시 쓰로틀링(Cache Throttling)” 또는 “캐시 압력(Cache Pressure)” 상황을 초래할 수 있다.

이러한 상황은 캐시가 가득 차서 새로운 데이터를 저장할 수 없게 되어 요청이 지연되거나, 대기 상태로 빠지는 문제를 일으킬 수 있다.

데이터량 증가

  • 각 사용자가 고유한 페이징 데이터를 요청할 경우, 캐시에 저장되는 데이터의 양이 빠르게 증가할 수 있다.
  • 다양한 페이징 요청(예: 페이지 번호, 페이지 크기, 필터링 조건 등)에 따라 각기 다른 캐시 항목이 생성되어, 캐시 공간을 많이 차지하게 된다.

캐시 용량 초과

  • 캐시는 일반적으로 제한된 메모리 공간을 사용한다. 사용자별로 많은 페이징 데이터를 캐시에 저장하면 캐시 용량이 초과될 수 있다.
  • 용량 초과로 인해 캐시가 새로운 항목을 수용할 수 없게 되면, 기존 항목을 제거하거나 요청이 대기 상태로 전환될 수 있다.

캐시 히트율 저하

  • 페이징 데이터를 캐시하는 경우, 사용자가 특정 페이지를 반복적으로 요청하지 않는 한 캐시 히트율이 낮아질 수 있다.
  • 낮은 캐시 히트율은 캐시의 효율성을 떨어뜨리며, 메모리 낭비로 이어질 수 있다.

사용자별 캐시 공간 경쟁

  • 다수의 사용자가 동일한 캐시 리소스를 공유할 때, 사용자별로 많은 데이터를 캐시에 저장하면 캐시 공간이 경쟁 상태가 되어, 중요한 데이터가 자주 제거될 수 있다.
  • 이로 인해 필요한 데이터가 캐시에 존재하지 않아, 다시 데이터베이스에서 가져오는 상황이 빈번해질 수 있다.

캐시 압력 해결방법

캐시 제한 설정

  • TTL(Time-To-Live): 캐시 항목에 TTL을 설정하여 일정 시간 후에 자동으로 만료되도록 설정할 수 있다. 이를 통해 오래된 캐시 데이터를 자동으로 제거하고, 새로운 항목을 저장할 공간을 확보할 수 있다.
  • 최대 캐시 크기 설정: 사용자별 페이징 데이터에 대해 캐시의 최대 크기를 설정하여, 용량이 초과되면 오래된 데이터를 자동으로 제거하도록 할 수 있다.

페이징 전략 최적화

  • 부분적 캐싱: 모든 페이지를 캐싱하기보다는 자주 요청되는 특정 페이지만을 캐싱하여, 캐시 공간을 효율적으로 사용할 수 있다.
  • 데이터베이스 인덱스 최적화: 캐시를 사용하는 대신, 데이터베이스 인덱스를 최적화하여 페이징 성능을 향상시킬 수 있다. 이렇게 하면 캐시 의존도를 줄이고, 데이터베이스에서 더 빠르게 결과를 가져올 수 있다.

분산 캐시 사용

  • Redis와 같은 분산 캐시 시스템을 사용하여, 여러 서버에 캐시 데이터를 분산시킬 수 있다. 이를 통해 한 서버의 메모리 용량 한계를 극복하고, 캐시 성능을 높일 수 있다.

캐시 우회 전략

  • 특정 조건에서 캐시를 우회하고 직접 데이터베이스에서 데이터를 가져오는 전략을 사용할 수 있다. 예를 들어, 페이징 요청이 매우 세분화된 경우 캐시를 우회하도록 설정할 수 있다.

캐시 압력 문제 예시 - 여러 사용자의 페이징 데이터

  • Redis 캐시에서는 다음과 같은 구조를 사용할 수 있다.
{
  "user123_page_1": {
    "userId": "user123",
    "page": 1,
    "pageSize": 10,
    "totalPages": 5,
    "data": [
      {
        "id": "item1",
        "name": "Item One",
        "description": "Description for item one."
      },
      {
        "id": "item2",
        "name": "Item Two",
        "description": "Description for item two."
      },
      ...
    ]
  },
  "user123_page_2": {
    "userId": "user123",
    "page": 2,
    "pageSize": 10,
    "totalPages": 5,
    "data": [
      {
        "id": "item11",
        "name": "Item Eleven",
        "description": "Description for item eleven."
      },
      {
        "id": "item12",
        "name": "Item Twelve",
        "description": "Description for item twelve."
      },
      ...
    ]
  },
  "user456_page_1": {
    "userId": "user456",
    "page": 1,
    "pageSize": 10,
    "totalPages": 3,
    "data": [
      {
        "id": "item21",
        "name": "Item Twenty-One",
        "description": "Description for item twenty-one."
      },
      {
        "id": "item22",
        "name": "Item Twenty-Two",
        "description": "Description for item twenty-two."
      },
      ...
    ]
  }
}

캐시 압력 문제 해결 예시

페이징 데이터 캐싱

사용자가 요청한 페이지에 해당하는 항목들의 ID 리스트를 캐시에 저장한다. 이 데이터는 상대적으로 작고, 페이징이 변경되지 않는 한 자주 재사용될 수 있다.

{ 
	"user123_page_1": ["item1", "item2", "item3"],
    "user123_page_2": ["item4", "item5", "item6"] 
}

아이템 데이터 캐싱

각 아이템에 대한 상세 데이터를 별도로 캐싱한다. 이렇게 하면 페이지를 요청할 때 페이지에 포함된 각 아이템의 상세 데이터를 개별적으로 가져올 수 있다.

{
  "item1": {
    "id": "item1",
    "name": "Item One",
    "description": "Description for item one."
  },
  "item2": {
    "id": "item2",
    "name": "Item Two",
    "description": "Description for item two."
  },
  "item3": {
    "id": "item3",
    "name": "Item Three",
    "description": "Description for item three."
  }
}

데이터 조회 및 조합

사용자가 특정 페이지를 요청하면 다음 단계로 데이터를 처리한다.

  1. 페이지 ID 리스트 가져오기
    • 먼저, 요청된 페이지에 해당하는 페이징 데이터를 가져온다.
    • 예를 들어, user123_page_1 요청 하여 페이징 데이터를 가져다.
  2. 아이템 상세 데이터 가져오기
    • 위에서 가져온 각 아이템 ID에 대해 캐시된 상세 데이터를 조회한다.
    • 예를 들어, item1, item2, item3 각각의 ID로 캐시에서 상세 정보를 가져온다.
  3. 데이터 조합
    • 가져온 아이템 상세 데이터를 조합하여 최종 결과를 생성한다. 이 데이터를 사용자에게 응답으로 반환한다.

캐시 백오프 전략

일반적으로 캐시에 접근할 때 캐시 미스(cache miss)가 발생하면, 시스템은 데이터를 캐시에 다시 적재하거나 원본 데이터베이스에서 데이터를 가져와야 한다. 이 과정에서 여러 클라이언트가 동시에 캐시에 접근하거나 요청을 보내면, 캐시 서버나 원본 데이터베이스에 과부하가 걸릴 수 있다.

 

캐시 백오프는 이러한 과부하를 방지하기 위해, 캐시 미스가 발생했을 때 재시도 요청의 빈도를 줄이거나 일정한 대기 시간을 두는 전략이다. 이렇게 하면 캐시 미스 발생 시 즉각적으로 원본 데이터베이스나 캐시 서버에 다량의 요청이 몰리지 않도록 하여, 시스템이 정상적으로 복구할 시간을 벌 수 있다.

 

캐시 백오프는 다음과 같은 전략이 있다.

 

  • 고정 대기 시간(Fixed Backoff):
    • 캐시 미스가 발생한 후 일정한 시간(예: 100ms) 동안 재시도를 지연시킨다.
  • 지수 백오프(Exponential Backoff):
    • 재시도 시간이 일정하지 않고, 시도할 때마다 대기 시간이 기하급수적으로 증가하는 방식이다. 예를 들어, 첫 번째 재시도는 100ms, 두 번째는 200ms, 세 번째는 400ms처럼 대기 시간을 점점 늘려간다.
  • 랜덤 백오프(Randomized Backoff):
    • 각 클라이언트가 캐시에 재시도할 때 랜덤하게 대기 시간을 설정하여 요청이 특정 시간에 몰리는 것을 방지한다.

참조

  • 팀스파르타코딩클럽 강의자료