[algorithm] 캐시 무효화 — 일반적인 해결책이 있습니까?

“컴퓨터 과학에는 캐시 무효화와 이름 지정이라는 두 가지 어려운 문제가 있습니다.”

필 칼튼

캐시를 무효화하는 일반적인 솔루션이나 방법이 있습니까? 언제 항목이 오래되었는지 알기 위해 항상 새로운 데이터를 확보 할 수 있습니까?

예를 들어 getData()파일에서 데이터를 가져 오는 함수 를 생각해보십시오 . 파일을 마지막으로 수정 한 시간을 기준으로 캐시하고 호출 될 때마다 확인합니다.
그런 다음 transformData()데이터를 변환하고 다음에 함수가 호출 될 때 결과를 캐시 하는 두 번째 함수를 추가합니다 . 파일에 대한 지식이 없습니다. 파일이 변경되면이 캐시가 유효하지 않게되는 종속성을 어떻게 추가합니까?

호출 getData()될 때마다 호출 transformData()하여 캐시를 빌드하는 데 사용 된 값과 비교할 수 있지만 결국 비용이 많이들 수 있습니다.



답변

당신이 말하는 것은 평생 의존성 체인입니다. 그것은 하나의 것이 다른 것에 의존하며 그것의 통제 밖에서 수정할 수 있습니다.

에서 멱등 함수가있는 경우 a, bto cwhere, if aand bare the same then cis same but the cost of check the cost bis high then you either :

  1. 언젠가 오래된 정보로 작업하고 항상 확인하지는 않는다는 것을 인정하십시오. b
  2. b가능한 한 빨리 확인하기 위해 최선을 다 하십시오.

케이크를 먹고 먹을 수는 없습니다 …

a맨 위에 기반으로 추가 캐시를 계층화 할 수 있다면 이는 초기 문제에 1 비트가 아닌 영향을 미칩니다. 1을 선택하면 자신에게 부여한 자유를 가지므로 더 많이 캐시 할 수 있지만 캐시 된 값의 유효성을 고려해야합니다 b. 2를 선택한 경우에도 b매번 확인해야 하지만 체크 아웃 a여부를 캐시로 되돌릴 수 있습니다 b.

캐시를 계층화하는 경우 결합 된 동작의 결과로 시스템의 ‘규칙’을 위반했는지 여부를 고려해야합니다.

당신이 알고있는 경우 a경우 항상 유효성을 가지고 b당신이 그렇게 (의사)와 같은 캐시를 정렬 할 수 있습니다 않습니다 :

private map<b,map<a,c>> cache //
private func realFunction    // (a,b) -> c

get(a, b)
{
    c result;
    map<a,c> endCache;
    if (cache[b] expired or not present)
    {
        remove all b -> * entries in cache;
        endCache = new map<a,c>();
        add to cache b -> endCache;
    }
    else
    {
        endCache = cache[b];
    }
    if (endCache[a] not present)     // important line
    {
        result = realFunction(a,b);
        endCache[a] = result;
    }
    else
    {
        result = endCache[a];
    }
    return result;
}

물론 연속 레이어링 (노니 x) 각 단계에서 새로 추가 입력의 유효성은 일치하므로만큼 간단하다 a: b대한 관계 x: b하고 x: a.

그러나 유효성이 완전히 독립적 (또는 순환 적) 인 세 개의 입력을 얻을 수 있으므로 계층화가 불가능합니다. 이것은 // 중요로 표시된 줄이 다음으로 변경되어야 함을 의미합니다.

(endCache [a]가 만료되었거나 존재하지 않는 경우)


답변

캐시 무효화의 문제는 우리가 알지 못하는 사이에 물건이 변경된다는 것입니다. 그래서 어떤 경우에는 그것에 대해 알고 우리에게 알릴 수있는 다른 것이 있다면 해결책이 가능합니다. 주어진 예제에서 getData 함수는 파일 시스템에 연결될 수 있습니다. 파일 시스템은 파일을 변경하는 프로세스에 관계없이 파일의 모든 변경 사항을 알고 있으며,이 구성 요소는 데이터를 변환하는 구성 요소에 다시 알릴 수 있습니다.

문제를 해결하기위한 일반적인 마법의 해결책은 없다고 생각합니다. 그러나 많은 실제 사례에서 “폴링”기반 접근 방식을 “인터럽트”기반 접근 방식으로 변환 할 수있는 기회가있을 수 있으며, 이는 문제를 간단히 해결할 수 있습니다.


답변

변환을 수행 할 때마다 getData ()를 수행한다면 캐시의 전체 이점을 제거한 것입니다.

예를 들어, 변환 된 데이터를 생성 할 때 데이터가 생성 된 파일의 파일 이름과 마지막으로 수정 된 시간도 저장하는 솔루션이 될 것 같습니다 (이미 getData (에서 반환 한 데이터 구조에 이미 저장했습니다). )이므로 해당 레코드를 transformData ())에서 반환 된 데이터 구조에 복사 한 다음 transformData ()를 다시 호출 할 때 파일의 마지막 수정 시간을 확인합니다.


답변

IMHO, FRP (Functional Reactive Programming)는 어떤 의미에서 캐시 무효화를 해결하는 일반적인 방법입니다.

이유는 다음과 같습니다. FRP 용어의 오래된 데이터를 글리치 라고합니다 . FRP의 목표 중 하나는 결함이 없음을 보장하는 것입니다.

FRP는이 ‘FRP의 본질’토크 와이 SO 답변 에서 더 자세히 설명됩니다 .

에서 이야기Cell s는 캐시 된 개체 / 법인을 대표하고는 Cell그것의 의존성 중 하나가 갱신되면 갱신됩니다.

FRP는 종속성 그래프와 관련된 배관 코드를 숨기고 부실이 없는지 확인합니다 Cell.


내가 생각할 수있는 또 다른 방법 (FRP와 다른)은 계산 된 값 (유형의 b)을 어떤 종류의 작가 모나드로 래핑하는 것입니다. Writer (Set (uuid)) b여기서 Set (uuid)(Haskell 표기법)은 계산 된 값이 b의존 하는 가변 값의 모든 식별자를 포함합니다 . 따라서 uuid계산 된 값이 b의존 하는 가변 값 / 변수 (예 : 데이터베이스의 행)를 식별하는 일종의 고유 식별자입니다 .

이 아이디어를 이러한 종류의 작성자 Monad에서 작동하는 결합 자와 결합하면 이러한 결합자를 사용하여 새로운 b. 이러한 콤비는 (의 특별 버전 말 filter테이크 작가의 모나드) 및 (uuid, a)입력으로 -s a에 의해 식별되는 가변 데이터 / 변수입니다 uuid.

때마다 당신이 “원래”데이터를 변경 그래서 (uuid, a)(있는 데이터베이스의 정규화 된 데이터 말하는 b유형의 계산 된 값이있는 계산 된) b다음에 포함 된 캐시를 무효화 할 수 있습니다 따라 b어떤 값 돌연변이 경우 a계산 된 b값이 따라 달라집니다 , Set (uuid)Writer Monad를 기반으로하면 언제 이런 일이 발생하는지 알 수 있기 때문입니다 .

따라서 주어진으로 무언가를 변경할 때마다이 uuid변이를 모든 캐시에 브로드 캐스트 b하고 said로 식별 된 변경 가능 값에 의존 하는 값 을 무효화합니다. uuid왜냐하면 b래핑 된 Writer 모나드가 이것이 bsaid uuid또는 아니.

물론 이것은 글을 쓰는 것보다 더 자주 읽는 경우에만 효과가 있습니다.


세 번째, 실용적인 접근 방식은 데이터베이스에서 구체화 된 뷰를 사용하고이를 캐시로 사용하는 것입니다. AFAIK 그들은 또한 무효화 문제를 해결하는 것을 목표로합니다. 물론 이것은 가변 데이터를 파생 데이터에 연결하는 작업을 제한합니다.


답변

지금 PostSharp메모 기능을 기반으로 한 접근 방식을 연구 하고 있습니다 . 저는 제 멘토를 지나서 실행했으며 그는 콘텐츠에 구애받지 않는 방식으로 캐싱을 잘 구현했다는 데 동의합니다.

모든 함수는 만료 기간을 지정하는 속성으로 표시 할 수 있습니다. 이러한 방식으로 표시된 각 함수는 메모되고 결과는 함수 호출의 해시와 키로 사용되는 매개 변수와 함께 캐시에 저장됩니다. 캐시 데이터 배포를 처리하는 백엔드에 Velocity 를 사용 하고 있습니다.


답변

항목이 오래되어 항상 새로운 데이터를 얻을 수 있도록 캐시를 생성하는 일반적인 솔루션이나 방법이 있습니까?

아니요, 모든 데이터가 다르기 때문입니다. 일부 데이터는 1 분 후, 일부는 1 시간 후 “부실”할 수 있으며 일부는 며칠 또는 몇 달 동안 괜찮을 수 있습니다.

특정의 예에 대해서는, 가장 간단한 솔루션은 모두에서 호출 파일에 대한 ‘캐시 검사’기능을 가지고있다 getDatatransformData.


답변

일반적인 해결책은 없지만 :

  • 캐시는 프록시 (풀) 역할을 할 수 있습니다. 캐시가 마지막 원본 변경의 타임 스탬프를 알고 있다고 가정하면 누군가를 호출 getData()하면 캐시는 원본에 마지막 변경의 타임 스탬프를 요청하고, 동일한 경우 캐시를 반환하고, 그렇지 않으면 원본 콘텐츠로 콘텐츠를 업데이트하고 콘텐츠를 반환합니다. (변형은 클라이언트가 요청에 대한 타임 스탬프를 직접 전송하는 것이며 소스는 타임 스탬프가 다른 경우에만 콘텐츠를 반환합니다.)

  • 여전히 알림 프로세스 (푸시)를 사용할 수 있으며, 캐시는 소스를 관찰하고, 소스가 변경되면 캐시에 알림을 보내고 “더티”플래그가 지정됩니다. 누군가 getData()캐시를 호출 하면 먼저 소스로 업데이트 될 것입니다. “dirty”플래그를 제거하십시오. 그런 다음 내용을 반환합니다.

일반적으로 말하는 선택은 다음에 따라 다릅니다.

  • 빈도 : 많은 호출 getData()이 푸시를 선호하므로 소스가 getTimestamp 함수에 의해 넘쳐나는 것을 방지합니다.
  • 소스에 대한 액세스 : 소스 모델을 소유하고 있습니까? 그렇지 않은 경우 알림 프로세스를 추가 할 수 없습니다.

참고 : 타임 스탬프를 사용하는 것이 http 프록시가 작동하는 전통적인 방식이므로 다른 접근 방식은 저장된 콘텐츠의 해시를 공유하는 것입니다. 내가 2 개의 엔티티가 함께 업데이트되는 것을 아는 유일한 방법은 내가 당신을 부르거나 (당기기) 당신이 나를 부르는 것입니다 … (푸시) 그게 전부입니다.