Datei locken



  • Sagen wir, wir schreiben einen Dienst, welcher formatiert Verzeichniseinträge exportieren soll.

    Wir wollen aber nicht jedes Mal scandir machen. Das ständige Auf- und Runtergehen der Verzeichnisse ist recht langsam (10000+ Einträge pro Verzeichnis) und zusätzlich müssen wir n + 1-mal free aufrufen, wenn wir die Liste wieder freigeben wollen.

    Besser wäre es, wenn für ein Verzeichnis eine Datei vorliegt, in der dann alle Einträge formatiert drinstehen. Das erlaubt den Kernel auch, seine Magie zu wirken (I/O-Cache).

    Jetzt packen wir Mutlithreading rein. Keine Prozesse. Jetzt wird's interessant - jeder Thread muss prüfen, ob die Cache-Datei vorhanden ist, und ob das Datum der letzten Änderungen nach dem Datum der letzten Änderung des Verzeichnisses liegt. Wenn ja, kann die Datei gesendet werden. Wenn nicht, muss der erste Thread für das Verzeichnis einen Lock auf einen Mutex anfordern. Wichtig ist, dass wir pthread_mutex_trylock verwenden, damit wir, wenn wir den Lock nicht bekommen haben, wissen, dass ein Thread bereits bei der Abarbeitung der Erstellung der Cache-Datei ist.

    Alle anderen Threads würden dann versuchen, über pthread_mutex_lock einen Lock auf den Mutex zu bekommen - was als Signal interpretiert werden kann, dass der Thread, welcher die Cache-Datei erstellen soll, fertig ist, und die Datei jetzt verfügbar ist.

    Warum nicht einfach die Cache-Datei exklusiv locken?
    Weil die Datei-Lock-Bibliotheken von POSIX und Linux "nutzloser Tand" sind, und das ist noch die schönste Bezeichnung, die ich finden konnte. Siehe hierzu und auch das. Weil wir nämlich keine Prozesse verwenden, sondern Threads.

    Allerdings: wir haben ja nicht nur ein Verzeichnis, sondern mehrere. Sprich, wir müssen erst mal eine Liste erstellen, in der dann drinsteht, welches Verzeichnis gerade abgearbeitet wird, und wenn es sich um das gleiche Verzeichnis handelt, muss dann wieder geraced werden, um den Mutex zu bekommen. Bonuspunkte in der Kategorie Komplexität, wenn diese Liste dynamisch gefüllt und geleert werden soll - kann ja sein, dass wir jederzeit neue Verzeichniseinträge erstellen. Eine statische Liste im Dienst ist dann so ein bisschen Kacke. Die dynamische Liste muss dann auch natürlich gelockt werden.

    Habe ich irgendetwas übersehen, und wenn nicht, habe ich einen Fehler in meinem Gedankengang/gibt es Dinge, die man einfacher/schneller/intelligenter lösen kann?

    EDIT: Dass zwischen dem "Datei existiert" und "Datei zurückgeben" natürlich auch sowas wie ein Lock passieren muss, ist mir klar - weil sonst ein anderer Thread die Datei löschen kann. Es geht mir hier um den Worst-Case, nicht um den Default-Case.



  • dachschaden schrieb:

    Sagen wir, wir schreiben einen Dienst, welcher formatiert Verzeichniseinträge exportieren soll.

    Bis hierhin kann ich noch folgen.

    dachschaden schrieb:

    Wir wollen aber nicht jedes Mal scandir machen. Das ständige Auf- und Runtergehen der Verzeichnisse ist recht langsam (10000+ Einträge pro Verzeichnis) und zusätzlich müssen wir n + 1-mal free aufrufen, wenn wir die Liste wieder freigeben wollen.

    Wie lange dauert das denn so pro Verzeichniseintrag?

    dachschaden schrieb:

    Jetzt wird's interessant - jeder Thread muss prüfen, ob die Cache-Datei vorhanden ist, und ob das Datum der letzten Änderungen nach dem Datum der letzten Änderung des Verzeichnisses liegt.

    Geht es hier darum eine funktionierende Lösung zu finden oder nur darum, etwas Interessantes zu machen? Warum muss der Cache überhaupt in einer Datei stehen und nicht einfach im Speicher des Dienstes?



  • TyRoXx schrieb:

    dachschaden schrieb:

    Wir wollen aber nicht jedes Mal scandir machen. Das ständige Auf- und Runtergehen der Verzeichnisse ist recht langsam (10000+ Einträge pro Verzeichnis) und zusätzlich müssen wir n + 1-mal free aufrufen, wenn wir die Liste wieder freigeben wollen.

    Wie lange dauert das denn so pro Verzeichniseintrag?

    Pro Verzeichniseintrag kann ich nicht sagen. Aber einmal das größte Verzeichnis durchzugehen und die Liste zu generieren dauert 16 Sekunden (da reden wir dann aber von ~ 10 Millionen Einträgen) bei 50%-Auslastung eines Kerns. Und das jedes Mal, wenn die Liste angefordert wird.

    TyRoXx schrieb:

    Geht es hier darum eine funktionierende Lösung zu finden oder nur darum, etwas Interessantes zu machen? Warum muss der Cache überhaupt in einer Datei stehen und nicht einfach im Speicher des Dienstes?

    1. Ersteres.
    2. Damit der Cache auch dann noch verfügbar ist, wenn der Dienst neugestartet werden muss. Die Verzeichniseinträge ändern sich nicht so häufig, wie sie angefragt werden - aber wenn sie geändert werden, dann muss die Liste auch direkt stehen.

    Und wenn der Kernel schlau ist, dann lässt er die Datei im I/O-Cache. Wenn die Liste dann später ausgegeben wird, muss nichts von der Platte geladen werden.


  • Mod

    Wie wäre es, den Cache zentraler zu organisieren? Also so etwas wie eine Datenbank für deinen Dienst. Dann hast du auch keine der anderen möglichen Probleme, die du anscheinend noch gar nicht gefunden hast:
    -Funktioniert auch ohne Schreibrechte
    -Kein Zumüllen der Verzeichnisse mit deinen Cachedateien



  • SeppJ schrieb:

    Wie wäre es, den Cache zentraler zu organisieren? Also so etwas wie eine Datenbank für deinen Dienst. Dann hast du auch keine der anderen möglichen Probleme, die du anscheinend noch gar nicht gefunden hast:
    -Funktioniert auch ohne Schreibrechte
    -Kein Zumüllen der Verzeichnisse mit deinen Cachedateien

    Der Ansatz hat leider zwei Probleme:
    - Datenbanksoftware würde wieder weitere Abhängigkeiten und Konfiguration bedeuten, die möglichst vermieden werden sollen. Wir wollen Komplexität minimieren und sie nicht verstecken.
    - Wenn Daten über Pipe oder Socket angefragt werden, würden wir die Daten schon gerne Zero-Copy schreiben. Selbst, wenn ich eine Datenbanksoftware konfigurieren würde, die mir garantiert, dass zumindest beim Selecten des Datensatzes direkt das File-Mapping zur Verfügung steht (bin mir nicht mal sicher, dass es sowas gibt) - habe ich später beim write(2) -Call wieder den Flaschenhals, dass der Kernel die Daten in seinen eigenen Speicher kopiert. Und das sind (n + x) * y Bytes, wobei y zwischen 10.000 und 10.000.000 variiert und (n + x) mindestens über 100 Zeichen pro Eintrag sind (wir also relativ schnell in den Gigabytebereich für eine einzelne Anfrage kommen können) - die dann sinnloserweise hin und zurück kopiert werden müssen.

    Die Cachedateien kann man auch direkt in einem Verzeichnis in /var/tmp ablegen. WO man die jetzt sucht, ist Jacke wie Hose - notfalls halt mit der Device-ID und der Inode des Verzeichnisses im Namen. Das Problem der Schreibrechte ist damit auch weg - wobei die man schon eigentlich haben will, wenn man neue Dateien ablegt, aber das ist ein minimales Problem.


Log in to reply