Frage

  

„Es gibt nur zwei schwere Probleme in der Informatik:. Cacheungültigmachungs und Dinge zu benennen“

Phil Karlton

Gibt es eine allgemeine Lösung oder Verfahren, um einen Cache ungültig zu machen; zu wissen, wann ein Eintrag veraltet ist, so dass Sie immer frische Daten bekommen garantiert werden?

Betrachten wir zum Beispiel eine Funktion getData(), die Daten aus einer Datei bekommt. Es speichert es auf der Grundlage der letzten Änderung der Datei, die es jedes Mal überprüft es nennt.
Dann fügen Sie eine zweite Funktion transformData(), die die Daten transformiert, und speichert das Ergebnis für das nächste Mal, wenn die Funktion aufgerufen wird. Es hat keine Kenntnis über die Datei - wie Sie die Abhängigkeit hinzufügen, dass, wenn die Datei geändert wird, dieser Cache wird ungültig

Sie könnten getData() jedes Mal transformData() nennen genannt wird und vergleichen ihn mit dem Wert, der verwendet wurde, um den Cache zu bauen, aber das sehr kostspielig sein könnte am Ende.

War es hilfreich?

Lösung

Was Sie sprechen, ist Lebensdauer Abhängigkeit Verkettungs, dass eine Sache auf einem anderen abhängig ist, die es sich außerhalb der Kontrolle geändert werden können.

Wenn Sie eine Idempotent Funktion von a, b zu c wo, wenn a und b gleich sind dann c das gleiche ist, aber die Kosten b die Überprüfung ist hoch dann Sie entweder:

  1. akzeptieren, dass Sie irgendwann mit aus aktuellen Informationen arbeiten und prüfe nicht immer b
  2. Sie Ihr Niveau besten b so schnell wie möglich
  3. machen Überprüfung

Sie können den Kuchen nicht essen und ihn ...

Wenn Sie einen zusätzlichen Cache basiert auf a über die obere Schicht kann dann betrifft dies das Ausgangsproblem nicht ein Bit. Wenn Sie 1 gewählt haben, dann haben Sie, was Freiheit, die Sie selbst gab und somit mehr zwischenzuspeichern, aber dürfen nicht vergessen, die Gültigkeit des im Cache gespeicherten Wert von b zu betrachten. Wenn Sie 2 gewählt haben, müssen Sie noch jedes Mal überprüfen b sondern kann auf den Cache für a zurückgreifen, wenn b Kontrollen.

Wenn Sie Caches Schicht müssen Sie überlegen, ob Sie die ‚Regeln‘ verstoßen des Systems als Folge des kombinierten Verhaltens.

Wenn Sie diese a wissen hat immer Gültigkeit, wenn b tut, dann können Sie Ihren Cache wie so (Pseudo-Code) anordnen:

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;
}

Offensichtlich aufeinanderfolgende Schichtung (sagt sie x) trivial ist, so lange in jeder Phase die Gültigkeit des neu hinzugefügten Eingangs entspricht den a: b Beziehung für x: b und x. a

Allerdings ist es durchaus möglich, dass Sie drei Eingänge, deren Gültigkeit war völlig unabhängig bekommen können (oder war zyklisch), so dass keine wäre möglich, die Schichtung. Dies würde bedeuten, die Linie markiert // wichtig würde sich ändern müssen, um zu

  

if (endCache [a] abgelaufen oder nicht vorhanden)

Andere Tipps

Das Problem in Cache-Annullierungs ist, dass Sachen Änderungen ohne uns darüber wissen. Also, in einigen Fällen ist eine Lösung möglich, wenn es eine andere Sache, die über sie weiß und uns mitteilen kann. In dem gegebenen Beispiel könnte die getData Funktion in das Dateisystem einhängen, die über alle Änderungen an Dateien nicht kennt, unabhängig davon, was Prozess ändert die Datei, und diese Komponente wiederum könnte die Komponente, die Transformationen der Daten informieren.

Ich glaube nicht, gibt es eine allgemeine Magie Fix das Problem weggehen zu machen. Aber in vielen praktischen Fällen sehr gut Gelegenheiten sein kann, einen „Polling“ -basierte Ansatz in eine „Unterbrechung“ -Basis zu verwandeln, das das Problem einfach weggehen kann.

Wenn Sie getData gehen () jedes Mal, wenn Sie die Transformation zu tun, dann haben Sie den gesamten Nutzen des Cache eliminieren.

Für Ihr Beispiel ist es wie eine Lösung scheint wäre, wenn Sie die transformierten Daten erzeugen, um auch die Dateinamen zu speichern und Zeitpunkt der letzten Änderung der Datei wurden die Daten erzeugen aus (Sie bereits gespeichert diese in welcher Datenstruktur zurückgegeben wurde von getData (), so dass Sie nur kopieren Sie diesen Datensatz in die Datenstruktur von transformData () zurückgegeben wird) und dann, wenn Sie transformData () aufrufen, überprüfen Sie die Zeit der letzten Änderung der Datei.

IMHO, Functional Reactive Programming (FRP) ist in gewisser Hinsicht eine allgemeine Weise Cache-Annullierungs zu lösen.

Hier ist der Grund: veraltete Daten in FRP Terminologie genannt wird, ein Glitch . Einer von FRP Zielen zu garantieren Abwesenheit von Störungen.

FRP wird näher erläutert in dieser 'Essenz der FRP' sprechen und in diesem SO Antwort .

In dem Gespräch die Cells ein im Cache gespeicherte Objekt repräsentieren / Entity und ein Cell ist aktualisiert, wenn ein Abhängigkeits seiner aufgefrischt wird.

FRP versteckt den Sanitär-Code mit dem Abhängigkeitsgraphen zugeordnet ist, und stellt sicher, dass es keine veralteten Cells.


Ein andere Art und Weise (abweichend von FRP), dass ich denken kann, wird den berechneten Wert Einwickeln (vom Typ b) in eine Art Schriftsteller Monad Writer (Set (uuid)) b wo Set (uuid) (Haskell-Notation) enthält alle die Kennungen der veränderbaren Werte, auf denen die berechneter Wert b abhängt. Also, uuid ist eine Art eine eindeutige Kennung, die angibt, die wandelbar Wert / Variable (sagen wir eine Zeile in einer Datenbank), auf dem die berechnete b abhängig ist.

Kombinieren Sie diese Idee mit Kombinatoren, die auf dieser Art von Schriftsteller Monad arbeiten und das könnte zu einer Art einer allgemeinen Cache-Annullierungs Lösung führen, wenn Sie nur diese combinators verwenden, um einen neuen b zu berechnen. Solche combinators (sagen eine spezielle Version von filter) Writer Monaden und (uuid, a)-s als Eingänge nehmen, wo a eine veränderbare Daten / variabel ist, durch uuid identifiziert.

Also jedes Mal, wenn Sie das „Original“ (uuid, a) Daten ändern (sagen die normalisierten Daten in einer Datenbank, aus der b berechnet wurde), auf dem der berechnete Wert vom Typ b hängt dann können Sie den Cache ungültig machen, die b enthält, wenn mutieren Sie irgendwelche Wert a, auf dem der berechnete b Wert abhängt, denn auf der Set (uuid) im Writer basiert Monad man kann sagen, wenn dies geschieht.

So, wann immer Sie mutieren etwas mit einem bestimmten uuid, übertragen Sie diese Mutation zu all dem Cache-s und sie ungültig macht die Werte b, die mit dem uuid identifiziert auf dem wandelbaren Wert abhängen, da die Writer Monade, in dem die b Dose gewickelt sagen, ob das b auf dem uuid hängt oder nicht.

Natürlich ist dies zahlt nur aus, wenn Sie viel öfter gelesen als Sie schreiben.


Ein dritte, praktisch, Ansatz zur Nutzung ist materialisierte Ansicht-s in Datenbanken und als Cache-es verwenden. AFAIK sie zielen auch darauf ab, das Ungültigkeits Problem zu lösen. Dies ist natürlich begrenzt, die Operationen, die die veränderbaren Daten an die abgeleiteten Daten verbinden.

Ich arbeite jetzt an einem Ansatz, der auf Postsharp und memoizing Funktionen . Ich habe es an meinem Mentor laufen, und er stimmt zu, dass es das Caching in einem Content-agnostischen Weise eine gute Umsetzung ist.

Jede Funktion kann mit einem Attribut gekennzeichnet werden, die ihre Verfallszeit angibt. Jede Funktion in dieser Art und Weise markiert ist memoized und das Ergebnis wird in den Cache gespeichert wird, mit einem Hash-Wert des Funktionsaufrufes und Parameter als Schlüssel verwendet. Ich verwende Geschwindigkeit für das Backend, die Verteilung der Griffe Cache-Daten.

  

Gibt es eine allgemeine Lösung oder Methode, um einen Cache zu schaffen, um zu wissen, wenn ein Eintrag veraltet ist, so sind Sie garantiert immer frische Daten?

Nein, weil alle Daten sind anders. Einige Daten können als „abgestanden“ sein nach einer Minute, einige nach einer Stunde, und einige gut für Tage oder Monate sein kann.

In Bezug auf Ihre speziellen Beispiel ist die einfachste Lösung, die eine ‚Cache-Überprüfung‘ Funktion für Dateien zu haben, die Sie rufen beide von getData und transformData.

Es gibt keine allgemeine Lösung, aber:

  • Sie Cache kann als Proxy (pull) handeln. Angenommen, Sie den Cache der letzten Ursprung Änderung Zeitstempel weiß, wenn jemand Anruf getData(), der Cache den Ursprung bitten um es letzte Änderung der Zeitstempel, wenn der gleiche, den Cache zurückgibt, andernfalls aktualisiert er seinen Inhalt mit der Quelle ein und gibt dessen Inhalt. (Eine Variante ist der Client direkt den Zeitstempel auf der Anforderung senden, würde die Quelle nur Inhalt zurück, wenn seine Zeitstempel unterschiedlich ist.)

  • Sie können nach wie vor eine Benachrichtigungsprozess verwenden (push), beachten Sie bitte die Cache, um die Quelle, wenn die Quelle ändert, ist es sendet eine Benachrichtigung an den Cache, die dann als „dirty“ markiert ist. Wenn jemand Anrufe getData() wird der Cache zuerst an die Quelle aktualisiert werden, entfernen Sie die „schmutzige“ Flagge; Rückkehr dessen Inhalt dann.

Die Wahl im Allgemeinen abhängig von:

  • Die Frequenz: viele Anrufe auf getData() würde einen Push bevorzugen so die Quelle zu vermeiden, durch eine getTimestamp Funktion geflutet werden
  • Ihr Zugriff auf die Quelle: Sind Sie das Source-Modell besitzen? Wenn nicht, stehen die Chancen, Sie keinen Benachrichtigungsprozess hinzufügen.

Hinweis: Da die Zeitstempel verwendet, ist die traditionelle Art und Weise http-Proxies arbeiten, teilt ein anderer Ansatz einen Hash des gespeicherten Inhalts. Der einzige Weg, ich weiß, für 2 Einheiten zusammen aktualisiert werden sind entweder ich Sie anrufen (pull) oder Sie rufen Sie mich an ... (push), das ist alles.

Cache ist hart, weil Sie beachten müssen: 1) der Cache mehrere Knoten, müssen Konsens für sie 2) Ungültigkeitszeit 3) Race-Bedingung, wenn multple get / set passieren

ist dies eine gute Lektüre: https://www.confluent.io / Blog / Drehen-the-Datenbank-inside-out-with-apache-samza /

Vielleicht Cache-Algorithmen blind wären die allgemeinste (Oder zumindest weniger Hardware-Konfiguration abhängig), da sie die schnellste Cache ersten und von dort weitermachen verwenden werden. Hier ist ein MIT-Vortrag über sie: Cache Oblivious Algorithmen

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top