Antwort an „Rolf B“ verfassen

Hallo Simone,

ich habe mich mit Fibers noch nicht viel beschäftigt, aber aus der PHP Doku und aus dem "Simpel-Scheduler" Beispiel von Ali Madadi (hier) geht für mich hervor, dass es sich hierbei um eine Möglichkeit handelt, kooperatives Multitasking zu implementieren.

Also: Fibers sind keine Threads!

PHP ist single-threaded. Und Multitasking ist etwas anderes als Multithreading!

Multitasking: Der Computer hat mehrere Tasks zu erledigen und verteilt seine Leistung mehr oder weniger gerecht darauf. Entweder dadurch, dass die Tasks explizit die Kontrolle abgeben (kooperativ) oder dadurch, dass die Hardware den Task unterbricht und das Betriebssystem auf einen anderen umschaltet (präemptiv). Das gelingt auch mit nur einem CPU Kern und ist dann deutlich einfacher zu bändigen, wenn mehrere Tasks an einer gemeinsamen Aufgabe werkeln.

Multithreading: Multitasking mit mehr als einer CPU. Zwei Tasks können echt parallel ausgeführt werden. Die Herausforderungen und Fehlermöglichkeiten sind deutlich komplexer. Race-Conditions und das Fehlschlagen scheinbar einfacher Operationen wie $i = $i+1 können vorkommen.

Madadi spricht zwar von Multithreading, aber das ist falsch. PHP führt zu jedem Zeitpunkt genau eine Codestelle aus. Entweder das Hauptprogramm, oder einen der Fibers.

Ein Fiber kann sich unterbrechen, dann kehrt die Ausführung zum Hauptthread zurück. Und nur der Hauptthread kann eine Fiber mit resume weiterlaufen lassen. Aber dann bleibt der Hauptthread genau dort stehen und erst dann, wenn der Fiber suspend aufruft oder endet, kommt der resume-Aufruf im Haupthhread wieder zurück.

D.h. mit Fibers kann man kooperatives Multitasking mit Datenaustausch implementieren. Oder, wie Madadi, ein präemptives Multitasking, wenn man die Task-Fibers über die den tick-Handler brutal unterbricht. Aber zu einem Zeitpunkt läuft immer nur ein Task, und das Hauptprogramm ist in der Pflicht, alle Fibers zu orchestrieren und dafür zu sorgen, dass alle aktiven Fibers irgendwann einen resume bekommen.

Du schreibst, dass Du JSON-Files für MYSQL aufbereitest. D.h. du liest eine JSON-Datei, machst was damit und schreibst das Ergebnis in die Datenbank?

Mal angenommen, dass MYSQLI_ASYNC bei Updates überhaupt greift, dann kannst Du natürlich N Fibers erstellen, die eine JSON-Datei lesen und asynchron ein Update anstoßen. Dass der Update asynchron ist, ist dabei entscheidend, denn andernfalls gewinnst Du gar nichts durch die Fibers. Denn das Einlesen und Verarbeiten der JSONs läuft synchron. Nach dem mysqli_query kann der Fiber sich mit Fiber::suspend() schlafen legen. Der Hauptthread muss dann, nachdem er N Fibers gestartet hat, alle Fibers im Kreis resumen. Diese pollen, ob eine Antwort da ist. Wenn ja wird sie verarbeitet, wenn nicht, muss weiter gewartet werden. Über ihren Rückgabewert kann die Fiber mitteilen, ob sie weiter warten muss oder nicht. Ist eine Fiber fertig, kann für diese Connection eine neue Fiber mit der nächsten Datei gestartet werden.

Alles sehr komplex, aufwändig zu entwickeln und schwierig zu testen. Aber immerhin kann MYSQL dann im Hintergrund arbeiten und die Daten wegschreiben.

Allzuviele Fibers solltest Du aber nicht starten. Denn zum einen bleibt die JSON-Verarbeitung synchron, und zum anderen ist es nicht gesund für die Performance einer DB, wenn sie von zu vielen Verbindungen INSERTs oder UPDATEs auf die gleiche Ressource bekommt. Die Frage nach der maximalen Fiber-Zahl ist deshalb eigentlich nicht relevant. Der SQL Server ist viel schneller am Ende als PHP.

Aus meiner Sicht ist die etwas bessere und unkompliziertere Idee auch eigentlich, einfach mehrere Kommandozeilen aufzumachen und in jeder eine Instanz des Importprogramms zu starten. Es muss irgendwie mitbekommen, welchen Bereich der JSONs es verarbeiten soll, und verarbeitet die dann schön und unkompliziert und synchron nacheinander. Vorteil 1: echtes Multithreading, auch für die JSON-Verarbeitung. Vorteil 2: Du musst keinen Fiber-Scheduler handschnitzen. Wieviele Kommandozeilen? Im Zweifelsfalle probieren, wo das Maximum ist. Du hast eine CPU mit 4 Cores und Hyperthreading. D.h. maximal 8 aktive Threads, wobei Hyperthreading schnell in die Knie geht, wenn man alle Threads nutzen will. Aber solange ein PHP auf die DB wartet, ist Zeit für andere PHP Threads, also könntest Du mit 5 oder sogar 6 PHPs davonkommen. Aber 6 könnte schon kontraproduktiv sein. Oder läuft die DB auf einem anderen Computer?

Die beste Idee ist aber vielleicht eine ganz andere: Einzelne INSERTs oder UPDATEs sind auch eigentlich nicht das Mittel der Wahl für einen Massen-Import. Dafür verwendet man LOAD DATA INFILE. Dein PHP sollte hergehen und die JSON-Files in eine Eingabedatei für LOAD übersetzen (oder mehrere, wenn es mehr als eine Table ist) und danach haust Du die Daten in einem Rutsch in die DB. Das geht natürlich nur, wenn Du eine LOAD-Berechtigung hast und wenn Du diese Dateien konfliktfrei vorbereiten kannst. Wenn jede JSON-Datei zu komplizierten Updates in der DB führt, dann geht das nicht. Alternativ zum LOAD Statement gibt's auch das Kommandozeilentool mysqlimport (mariadb-import) für Massenimports. Wenn Du sowas öfter machst und entsprechende Rechte hast, solltest Du Dich definitiv damit beschäftigen.

Ich selbst habe das auf MYSQL noch nicht gemacht, meine Massendatenzeiten waren auf IBM Mainframes mit DB2, aber wenn ich da regelmäßig in einem Batchjob eine Million Datensätze hätte INSERTen wollen, dann wären mir die DB-Admins sehr schnell und sehr lautstark auf's Dach gestiegen. Sowas bremst die Kiste nämlich gewaltig aus.

Rolf

--
sumpsi - posui - obstruxi
freiwillig, öffentlich sichtbar
freiwillig, öffentlich sichtbar
freiwillig, öffentlich sichtbar

Ihre Identität in einem Cookie zu speichern erlaubt es Ihnen, Ihre Beiträge zu editieren. Außerdem müssen Sie dann bei neuen Beiträgen nicht mehr die Felder Name, E-Mail und Homepage ausfüllen.

abbrechen