datenbankler: Ich brauche mal ein wenig Inspiration, Datenbankabfrage Cachen

Hallo ich versuche schon seit geraumer Zeit eine einfache, performante Lösung zu finden.
Ich habe eine einfache Datenbankabfrage:
select date from user where username="blabla" LIMIT 1

Die Abfrage wird sehr oft durchgeführt in Spitzenzeiten bis zu 30 mal die Sekunde.
Jetzt suche ich nach einer Lösung die mir zu dem Username 1 Stunde das Datum zwischespeichert und das soll Skiptübergreifend und Benutzerübergreifend funktionieren.

Ich stelle mir sowas vor wie:

if exist(cache["user"])
{
  date = cache["user"]
}
else
{
 ...langsamer Code
}

Dabei soll es egal sein wer die SQL Abfrage auslöst, mit Sessionvariablen, Cookies, GET gehts dann schon mal nicht. Eine Datei anlegen wäre zu langsam.
Ich habe bei meinem Provider mal nach den http://www.php.net/manual/de/book.apc.php Funktionen angefragt.

Gibt es keine einfache Lösung für dieses scheinbar einfache Problem?

  1. Hello,

    Du möchtest die Abarbeitung eines Problems optimieren, es also so genau wie möglich lösen. Uns gibst Du aber nur eine sehr ungenaue Beschreibung dieses Problems, knallst uns ein einzelnes SQL-Select vor den Latz, stellst Behauptungen auf, die wir so nicht nachvollziehen können ("Eine Datei anlegen wäre zu langsam.") und erwartest nun die Superlösung.

    So läuft das nicht.

    Bitte schildere deine Aufgabenstellung so ausführlich, wie möglich. Zeige uns alle Abfragen, die über einen Zeitraum ersetzt werden sollen, in wessen Hoheitsbereich sie ausgeführt werden, wie die Clients authentifiziert werden, usw.

    Warum werden die Daten überhaupt in einer Datenbank gespeichert, wenn sie sich nicht ändern? Oder anders herum gefragt, wenn Du meinen Hinweis nicht verstehst: wann ändern sich die Daten in der Datenbank und welche Relevanz hat das für Deine Aufgabe?

    Liebe Grüße aus dem schönen Oberharz

    Tom vom Berg

    --
     ☻_
    /▌
    / \ Nur selber lernen macht schlau
    http://bergpost.annerschbarrich.de
    1. Also genauer kann ich es fast nicht mehr ausdrücken,aber wenn es dich so brennend interessiert gibt es noch ein paar Infos am Rande.

      Es sind 3 Personen im Spiel, ich der Dateiinhaber, der Einsetzende und der Betrachter.
      Beim Aufruf einer Datei durch den Betrachter wird geprüft ob der "Einsetzende" der Datei aktuell die Berechtigung hat diese Datei zu nutzen. Das Datum ist eine Art Verfallsdatum welches verhindern soll das die Datei nach Ablauf des Nutzungsrechts weiter eingesetzt wird. Das Datum kann verlängert werden, aber eine stündliche Überprüfung würde völlig ausreichen.

      Dieses System ist schon viele Jahre im Einsatz und kann so nicht mehr geändert werden. Wie die Berechtigung allerdings geprüft wird schon.
      Im Endeffekt geht es wirklich nur darum eine Variable irgendwo zwischenzuspeichern. Die Prüfung mit Datenbankabfrage konnte ich schon auf unter 2ms drücken. Durch die Vermeidung einer MySQL Abfrage erwarte ich unter 1ms zu kommen. Von daher nehme ich an ein Dateizugriff wäre langsamer, oder irre ich mich, werde Dateien evtl. automatisch gecached?

      Hello,

      Du möchtest die Abarbeitung eines Problems optimieren, es also so genau wie möglich lösen. Uns gibst Du aber nur eine sehr ungenaue Beschreibung dieses Problems, knallst uns ein einzelnes SQL-Select vor den Latz, stellst Behauptungen auf, die wir so nicht nachvollziehen können ("Eine Datei anlegen wäre zu langsam.") und erwartest nun die Superlösung.

      So läuft das nicht.

      Bitte schildere deine Aufgabenstellung so ausführlich, wie möglich. Zeige uns alle Abfragen, die über einen Zeitraum ersetzt werden sollen, in wessen Hoheitsbereich sie ausgeführt werden, wie die Clients authentifiziert werden, usw.

      Warum werden die Daten überhaupt in einer Datenbank gespeichert, wenn sie sich nicht ändern? Oder anders herum gefragt, wenn Du meinen Hinweis nicht verstehst: wann ändern sich die Daten in der Datenbank und welche Relevanz hat das für Deine Aufgabe?

      Liebe Grüße aus dem schönen Oberharz

      Tom vom Berg

      1. Hi!

        Von daher nehme ich an ein Dateizugriff wäre langsamer, oder irre ich mich, werde Dateien evtl. automatisch gecached?

        Die Frage lässt sich vielleicht theoretisch erörtern, aber was dann praktisch auf deinem System funktioniert und was nicht, weiß nur ein Hellseher oder einer der es ausprobiert hat. Was hindert dich denn daran, selbst eine Messung zu veranstalten, zumal du das ja für die DBMS-Abfrage auch schon getan hast.

        Und bitte zitiere nur das, worauf du dich konkret beziehst und nicht im TOFU-Stil.

        Lo!

      2. Hello,

        Im Endeffekt geht es wirklich nur darum eine Variable irgendwo zwischenzuspeichern. Die Prüfung mit Datenbankabfrage konnte ich schon auf unter 2ms drücken. Durch die Vermeidung einer MySQL Abfrage erwarte ich unter 1ms zu kommen. Von daher nehme ich an ein Dateizugriff wäre langsamer, oder irre ich mich, werde Dateien evtl. automatisch gecached?

        Ob Daten, die auf der HDD geschrieben stehen, bei einem Zugriff automatisch gecached werden, das hängt vom verwendeten Dateisystem und seinen Komponenten ab, und welche Befehle aus dem Befehlssatz die Software dann benutzt.

        Bei einem Schreibzugriff wird oft auf direkt die Platte zurückgeschrieben, aber eben nicht immer. Darum soll man ja Filebuffer leeren bzw. die Datei schließen, bevor man das Unterprogramm beendet oder den Strom ausschaltet.

        Aus PHP-Sicht wird die Datei immer geschlossen, wenn das Script endet. Also müsste spätestens dann auch geschreiben werden. Ob aber das darunterliegende Filesystem nun den Schreibvorgang auch tatsächlich hart durchführt, oder solange wartet, bis genügend Schreibanforderungen im selben Bezirk der Platte vorliegen, hängt vom System ab. Elevator Seeking arbeitet z. B. mit einem solchen Buffer. Schreib- und Leseanforderungen werden nach Bezirken auf der Platte sortiert und gemeinschaftlich abgewickelt. Solange miss der Hauptspeicher herhalten.

        Wenn ich nun aber betrachte, dass ein Reqest über öffentliche Netze wohl mindestens 50ms benötigt, um beantwortet zu werden, relativiert sich Dein Anliegen schon.

        Um eine Optimierung der Abarbeitung vornehmen zu können, müsstest Du trotzdem etwas fleißiger sein und uns z.B. aussagefähige Zeitdiagramme zeigen. Für den Anfang reicht eine qualitative Darstellung, also die Darstellung der möglichen Szenarien. Welcher Client greift wann (in welcher Reihenfolge) auf was zu und was muss geschehen - Nur lesen, nur schreiben, lesen und schreiben = gezielt verändern?

        Auf Zuruf kann man sowas nur selten richtig verstehen.

        Liebe Grüße aus dem schönen Oberharz

        Tom vom Berg

        --
         ☻_
        /▌
        / \ Nur selber lernen macht schlau
        http://bergpost.annerschbarrich.de
        1. Also ich habe versucht der Sachverhalt mal in einem Diagramm darzustellen:
          http://tinyurl.com/38a6vcf
          Die Anzahl der Betrachter sind um ein VielVielVielfaches höher als die Anzahl der Nutzer, allerdings löst nicht jeder Betrachter einer Berechtigungsprüfung aus. Da dies von dem Dateialter und dem Nutzerstatus zum Zeitpunkt der Erstellung abhängt.

          Ich konnte jetzt durch dieses einfache Konstrukt:

          if (file_exists("../berechtigungen/user.txt"))
          {
                $date = file_get_contents("../berechtigungen/user.txt");
          }else{
          mysqlabfrage & Datei anlegen
          }
          Die Zeit der Berechtigungsprüfung von einigen Milisekunden auf 0,7-0,8ms senken. Auf meiner lokalen Xampp installation wie auch auf dem Server stelle ich eine Gewinn um ca. Faktor 3 fest. Das finde ich doch schon ganz ordentlich.
          Jetzt muss ich mir nur noch überlegen wie ich die Dateien verwalte. Man könnte diese ja zum Beispiel nur löschen lassen wenn sich an der Datenbank etwas ändert, oder nach einem bestimmten Dateialter die Datei neu bschreiben lassen.

          1. Hi!

            if (file_exists("../berechtigungen/user.txt"))
            {
                  $date = file_get_contents("../berechtigungen/user.txt");
            }else{

            Du kannst dir noch einen Dateisystemzugriff sparen, wenn du direkt file_get_contents() ausführst. Wenn die Datei nicht vorhanden/lesbar ist, gibt die Funktion false zurück, auf das du testen kannst. Zum Unterscheiden von einer leeren Datei musst du aber einen typsicheren Vergleich verwenden.

            if (($date = file_get_contents("../berechtigungen/user.txt")) === false)
              mysqlabfrage

            Lo!

            1. Wunderbar! Das hat nochmal 0,1ms gebracht. Jetzt sind wir schon bei 0,6-0,7ms ich denke damit sollte es ausreichen um erstmal wieder eine Weile Ruhe zu haben. Vielen Dank!

              Hi!

              if (file_exists("../berechtigungen/user.txt"))
              {
                    $date = file_get_contents("../berechtigungen/user.txt");
              }else{

              Du kannst dir noch einen Dateisystemzugriff sparen, wenn du direkt file_get_contents() ausführst. Wenn die Datei nicht vorhanden/lesbar ist, gibt die Funktion false zurück, auf das du testen kannst. Zum Unterscheiden von einer leeren Datei musst du aber einen typsicheren Vergleich verwenden.

              if (($date = file_get_contents("../berechtigungen/user.txt")) === false)
                mysqlabfrage

              Lo!

            2. Hello,

              if (file_exists("../berechtigungen/user.txt"))
              {
                    $date = file_get_contents("../berechtigungen/user.txt");
              }else{

              Du kannst

              Anmerkung von Mister Tocttou[1]: Du musst!

              dir noch einen Dateisystemzugriff sparen, wenn du direkt file_get_contents() ausführst. Wenn die Datei nicht vorhanden/lesbar ist, gibt die Funktion false zurück, auf das du testen kannst. Zum Unterscheiden von einer leeren Datei musst du aber einen typsicheren Vergleich verwenden.

              if (($date = file_get_contents("../berechtigungen/user.txt")) === false)
                mysqlabfrage

              Und wenn wir schon mal dabei sind: file_get_contents() ist nur bedingt multithreadingfest.
              Betrachtet werden muss ja immer das Pärchen aus Lese- und Schreibfunktion. Solange die nicht mit einem gemeinsamen Handle arbeiten können, oder ein anderer _gemeinsamer_ Mechanismus für die Zugriffskontrolle besteht, bleibt das TOCTTOU-Problem bestehen.

              http://en.wikipedia.org/wiki/Time-of-check-to-time-of-use
              http://aktuell.de.selfhtml.org/artikel/programmiertechnik/dateisperren/

              Es ist also angeraten, dass der OP mit den klassischen Funktionen fopen(), flock(), fread(), fwrite() und fclose() arbeitet.

              [1] so hattest Du mich doch neulich genannt, oder?

              Liebe Grüße aus dem schönen Oberharz

              Tom vom Berg

              --
               ☻_
              /▌
              / \ Nur selber lernen macht schlau
              http://bergpost.annerschbarrich.de
              1. Ich bin mir nicht sicher ob ich das jetzt richtig verstanden habe.
                Mit file_get_contents() lese ich ja nur, wenn ich die Datei aber mit fopen(), flock(), fwrite() und fclose() anlegen will könnte ja sein das fopen die Datei schon angelegt hat.
                file_get_contents() findet schon eine noch leere Datei und wird das Datum auf 0 setzen.
                Wie kann ich das umgehen ohne unixdatum nochmal auf Plausibilität zu prüfen und erneut auslesen zu lassen.
                Was passiert wenn zwei User gleichzeitig die selbe Datei anlegen wollen? Ich bin ein wenig verwirrt.

                Hello,

                if (file_exists("../berechtigungen/user.txt"))
                {
                      $date = file_get_contents("../berechtigungen/user.txt");
                }else{

                Du kannst

                Anmerkung von Mister Tocttou[1]: Du musst!

                dir noch einen Dateisystemzugriff sparen, wenn du direkt file_get_contents() ausführst. Wenn die Datei nicht vorhanden/lesbar ist, gibt die Funktion false zurück, auf das du testen kannst. Zum Unterscheiden von einer leeren Datei musst du aber einen typsicheren Vergleich verwenden.

                if (($date = file_get_contents("../berechtigungen/user.txt")) === false)
                  mysqlabfrage

                Und wenn wir schon mal dabei sind: file_get_contents() ist nur bedingt multithreadingfest.
                Betrachtet werden muss ja immer das Pärchen aus Lese- und Schreibfunktion. Solange die nicht mit einem gemeinsamen Handle arbeiten können, oder ein anderer _gemeinsamer_ Mechanismus für die Zugriffskontrolle besteht, bleibt das TOCTTOU-Problem bestehen.

                http://en.wikipedia.org/wiki/Time-of-check-to-time-of-use
                http://aktuell.de.selfhtml.org/artikel/programmiertechnik/dateisperren/

                Es ist also angeraten, dass der OP mit den klassischen Funktionen fopen(), flock(), fread(), fwrite() und fclose() arbeitet.

                [1] so hattest Du mich doch neulich genannt, oder?

                Liebe Grüße aus dem schönen Oberharz

                Tom vom Berg

                1. Hello,

                  Ich bin mir nicht sicher ob ich das jetzt richtig verstanden habe.
                  Mit file_get_contents() lese ich ja nur, wenn ich die Datei aber mit fopen(), flock(), fwrite() und fclose() anlegen will könnte ja sein das fopen die Datei schon angelegt hat.
                  file_get_contents() findet schon eine noch leere Datei und wird das Datum auf 0 setzen.
                  Wie kann ich das umgehen ohne unixdatum nochmal auf Plausibilität zu prüfen und erneut auslesen zu lassen.
                  Was passiert wenn zwei User gleichzeitig die selbe Datei anlegen wollen? Ich bin ein wenig verwirrt.

                  Schau die die möglichen Schalter von fopen() an
                  http://de.php.net/manual/en/function.fopen.php

                  Der Filemode "xb+" oder der neue Modus "cb+" könnten für dich die richtigen sein. Ob das 'b' wirklich noch notwendig ist, um auch auf Windows sauber zu arbeiten, habe ich noch nicht wieder untersucht. Schaden sollte es mMn aber nicht.

                  Es gewinnt der User, der bei Verwendung von 'x' schneller ist mit dem Anlegen und bei Verwendung von 'c' schneller ist mit der Benutzung von flock().

                  Welche Variante die richtige ist, oder ob man noch mehr basteln muss, um konkurrierenden Betrieb abzufedern, ergibt sich erst nach genauerer Analyse der Aufgabenstellung.

                  Daher solltest Du ja mal die möglichen Zugriffs-Szenarien als Zeitdiagramm darstellen.

                  Liebe Grüße aus dem schönen Oberharz

                  Tom vom Berg

                  --
                   ☻_
                  /▌
                  / \ Nur selber lernen macht schlau
                  http://bergpost.annerschbarrich.de
  2. Hi!

    Ich habe bei meinem Provider mal nach den http://www.php.net/manual/de/book.apc.php Funktionen angefragt.
    Gibt es keine einfache Lösung für dieses scheinbar einfache Problem?

    APC ist schon eine der Lösungen, Shared Memory wäre eine andere. Beide Lösungen wirst du wahrscheinlich beim Massenhoster nicht bekommen.

    Sessions funktionieren nicht nur mit Client-Bindung. Du kannst eine Session auch auf eine feste SID öffnen. Sessions haben gegenüber nur zum Lesen geöffneter Dateien jedoch zusätzlich den Nachteil, dass sie exklusiv geöffnet werden, auch wenn du nur daraus lesen willst. Die anderen Prozesse warten, während die Session von einer anderen Script-Instanz geöffnet worden ist. Auch bei selbst angelegten Dateien musst du, besonders bei diesen vielen gleichzeitigen Zugriffen einen Locking-Mechanismus für die Zeit des Datenänderns vorsehen. Inwieweit das bei den Cache/ShMem-Lösungen notwendig ist, musst du selbst herausfinden.

    Lo!