Sven Mörs: PHP, Ajax XML Produkte einzeln laden

Guten Tag,

habe riesige XML Dateien teilweise 900MB die will ich gerne Parsen und in der Datenbank speichern. Will gerne jedes Einzelne "Produkt" Speichern und ein Fortschritt Balken anzeigen lassen. Hole als erstes die Anzahl der Produkte per Ajax Request. Danach werden die Produkte einzeln gespeichert in der Datenbank.

Mache es deswegen so, dass die Webseite nicht stehen bleibt wenn ich alles auf einmal in einer Schleife auslese und in der Datenbank Speicher. Kann ja bis zu 15 Minuten dauern wenn über 50.000 Produkte drinne stehen mit Beschreibung etc. Problem ist nur das die Variable wo die XML geladen wird nach dem 2 Request wieder leer ist.

		/*Produktanzahl*/
		if ($_POST["func"] == "GetProductCount")
		{
			$loadXML = simplexml_load_file('shops/'.$sauber["filename"]);

			$shopID = "0";
			$sql = "DELETE FROM `products` WHERE `shopid`='".$shopID."';";
			$result = mysql_query($sql);

			$arr["count"] = count($loadXML->Product);

			echo json_encode($arr);
		}
		/*ENDE*/

		/*Produkte speichern*/
		if ($_POST["func"] === "SaveProduct")
		{		
			$Product = $loadXML->Product[$sauber["index"]];
                        ...


$loadXML ist bei "SaveProduct" wieder leer, die Variable ist Global Definiert in der index.php. Die ganze Webseite läuft mit Ajax die Index wird nur einmal geladen und deswegen sollte es eigentlich funktionieren. Andere Variablen funktionieren ja auch, warum geht das mit der XML nicht so?

  1. Tach!

    $loadXML ist bei "SaveProduct" wieder leer, die Variable ist Global Definiert in der index.php. Die ganze Webseite läuft mit Ajax die Index wird nur einmal geladen und deswegen sollte es eigentlich funktionieren. Andere Variablen funktionieren ja auch, warum geht das mit der XML nicht so?

    Ich mutmaße, dass du nicht berücksichtigst, dass jeder Ajax-Request ein erneuter Aufruf des serverseitigen Scripts ist. Es bleibt ja nichts zwischen zwei Requests bestehen, das nicht in einer Session zwischengelagert wurde. Oder das neu abgefragt wird.

    dedlfix.

    1. Danke für die Antwort. Auch wenn $loadXML in der index.php Definiert ist? Wird die echt bei jeden Ajax-Request geleert? Was mich verwirrt, habe ja noch eine andere Variable wo JSON Daten gespeichert sind. Die funktioniert ja auch bei jeden erneuten Ajax-Request. Deswegen verwirrt mich das etwas, warum das mit der XML nicht funktioniert. Prüfe das aber nochmal ob das wirklich so stimmt.

      Frage 1: Kann / Darf man die XML in einer Session speichern mit 1GB größe?

      Frage 2: Gibt es noch eine andere Möglichkeit?

      Gruß Sven

      1. Tach!

        Danke für die Antwort. Auch wenn $loadXML in der index.php Definiert ist? Wird die echt bei jeden Ajax-Request geleert?

        Das was du mit einem Ajax-Request aufrufst, wird behandelt wie jeder andere Aufruf auch. PHP startet, Script läuft durch, PHP wird beendet. Es gibt dabei keine Querbeziehungen zu anderen Requests oder parallel laufenden Scripts.

        Was mich verwirrt, habe ja noch eine andere Variable wo JSON Daten gespeichert sind. Die funktioniert ja auch bei jeden erneuten Ajax-Request. Deswegen verwirrt mich das etwas, warum das mit der XML nicht funktioniert.

        Da machst du möglicherweise was anders, oder beobachtest das vielleicht nicht richtig.

        Frage 1: Kann / Darf man die XML in einer Session speichern mit 1GB größe?

        Dürfen darf man. Können ist eine Frage der technischen Möglichkeiten, wie genügend Platz im Session-Verzeichnis. Und gegebenenfalls Größenbeschränkungen seitens PHP. Die sollten aber im Handbuch erwähnt sein.

        Frage 2: Gibt es noch eine andere Möglichkeit?

        Bestimmt, aber mir fallen da grad nur noch komplexere Szenarien ein (z.B. Websocket).

        dedlfix.

        1. Danke. Das funktioniert soweit. Was ich jetzt noch für ein Problem habe, ich kann per Index kein Produkt aufrufen:

          $Product = $loadXML->Product[$sauber["index"]];
          

          Erste mal das ich mit XML Arbeite. Habe bei Google gefunden mit ->children() aber das erkennt der garnicht. Habe folgendes probiert:

          $Products = $loadXML->Product->children();
          $Product = $Products[$sauber["index"]];
          

          Was mache ich da falsch? Bzw. wie kann ich drauf zugreifen mit Index?

          1. Tach!

            Was mache ich da falsch? Bzw. wie kann ich drauf zugreifen mit Index?

            Ich kann dich da nur an das PHP-Handbuch verweisen. SimpleXML arbeitet viel mit Magic Methods und ist damit weder in richtiges Array noch ein richtiges Objekt. Zugriffe funktionieren nur/aber so wie im PHP-Handbuch beschrieben. Da gibts auch ausreichend Beispiele für die gängigen Elemente in XML-Daten.

            dedlfix.

            1. Danke werde da nochmal reinschauen. Danke für deine Hilfe.

          2. Okay... und bei der 900MB großen XML bekomme ich Memory Out, kann er erst garnicht laden. Das ist nicht gut. Da muss ich wohl erst eine Software schreiben, die die XML unterteilt 😕

            1. Tach!

              Okay... und bei der 900MB großen XML bekomme ich Memory Out, kann er erst garnicht laden. Das ist nicht gut. Da muss ich wohl erst eine Software schreiben, die die XML unterteilt 😕

              PHP hat da eine Konfigurationsoption namens memory_limit.

              dedlfix.

              1. Habe ich schon auf 2GB gesetzt, trotzdem der Fehler. Ich werde einfach nochmal etwas Googlen, hast mir schon sehr geholfen. Danke nochmal.

                1. Tach!

                  Habe ich schon auf 2GB gesetzt, trotzdem der Fehler. Ich werde einfach nochmal etwas Googlen, hast mir schon sehr geholfen. Danke nochmal.

                  Da kann gut und gerne ein Vielfaches der Dateigröße an Speicherverbrauch entstehen. Die Datei wird ja nicht plain im Speicher gehalten, sondern als XML-Baum.

                  dedlfix.

                  1. Habe es auf 16GB gestellt, immer noch :D. Denke bei so großen Dateien ist es einfach besser wenn ich mir eine Software schreibe dafür.

                    1. Schau Dir mal XMLReader und XMLWriter an. Die sind viel näher am nackten Blech und damit kannst Du ein XML-Dokument seriell lesen oder schreiben, ohne es komplett im Speicher zu halten. Du kannst nur nicht rückwärts laufen, es ist eine sequenzielle Verarbeitung. Im XmlReader kannst Du Dich von Product zu Product hangeln, auf das outer XML zugreifen und es entweder per File IO oder mit dem XmlWriter in eine Datei schreiben.

                      Ich weiß nicht wie schnell der XMLReader ist, er müsste ziemlich fix agieren. Ggf. brauchst Du keinen File-Splitter, sondern es reicht, wenn Du pro Runde (siehe meinen Vorschlag mit dem Startindex) einfach soviele Product Knoten überliest wie der Startindex besagt und ab da anfängst zu importieren, bis die Zeit für eine Runde um ist.

                      Rolf

                      1. Danke werde ich mir mal anschauen!

            2. Hello,

              Okay... und bei der 900MB großen XML bekomme ich Memory Out, kann er erst garnicht laden. Das ist nicht gut. Da muss ich wohl erst eine Software schreiben, die die XML unterteilt 😕

              Genau. Da benötigst Du einen Index auf Start- und Endnodes von Blöcken, die noch in den Speicher passen. Beispiel

              Liebe Grüße
              Tom S.

              --
              Es gibt nichts Gutes, außer man tut es
              Andersdenkende waren noch nie beliebt, aber meistens diejenigen, die die Freiheit vorangebracht haben.
          3. Bist Du sicher, dass Du hier auf dem richtigen Weg bist? Also, so rein prinzipiell... Für eine XML-Datei mit 1000 Produkten liest Du die Datei 1001 mal ein, das ist komplett ineffizient. Aber dazu nachher mehr.

            Die Indexierung $loadxml->Product[$sauber["index"]] sollte eigentlich funktionieren. Wenn dein XML in etwa so aussieht:

            <?xml version='1.0'?>
            <Products>
              <Product>
                ...
              </Product>
              <Product>
                ...
              </Product>
              ...
            </Products>
            

            dann sollte $loadXml->Product ein Array mit diesen Produkten sein, das Indexe von 0 bis n-1 hat (mit n=Anzahl von Produkten). Es sollte auch wurscht sein, ob Du auf Product[0] oder Product["0"] zugreifst, d.h. es ist egal, ob $sauber["index"] einen String oder ein integer enthält. Es ist nur kritisch, wenn Leerstellen dazukommen. Product["2"] funktioniert, Product[" 2"] nicht.

            Product ist allerdings kein Array, sondern ein Objekt. Ich vermute, es implementiert das ArrayAccess Interface um ein arrayoides Verhalten zu zeigen... Es wäre daher sicherer, auf $loadXml->Product[intval($sauber["index"])] zuzugreifen.

            So, aber nun Tacheles - es ist ineffizient, was Du da tust. Allein die einzelnen Ajax-Aufrufe bremsen den Import aus, auch wenn gar nichts weiter passiert. Das Laden und Parsen von 50.000 Produkten aus einer Datei kostet nochmal ordentlich. Ich würde Dir empfehlen, dem SaveProduct-Aufruf einen STARTINDEX mitzugeben. Der merkt sich dann den aktuellen Zeitpunkt, lädt die XML-Datei und läuft los. Nach jedem Produkt misst er, wieviel Zeit vergangen ist. Wird es zu viel (das ist etwas, was von deinem Server und deinen persönlichen Vorlieben abhängt), beendet er die aktuelle Runde und gibt den nächsten Startindex an den Client zurück. Der zeigt an "nnn Sätze importiert" (wobei nnn praktischerweise mit dem Startindex identisch ist) und startet den nächsten Import.

            Wenn Du bei 50.000 Produkten von 15 Minuten ausgehst, schaffst Du mehr als 50 Produkte pro Sekunde, d.h. wenn Du den Ajax-Call 20 Sekunden lang laufen lässt, hast Du in einem Rutsch 1000 Produkte drin.

            Rolf

            1. Vielen Dank für die vielen Antworten. An Alle! Struktur ist immer gleich und sieht genauso aus wie die oben geschrieben hast. Werde das mal später testen.

      2. Hello,

        Frage 1: Kann / Darf man die XML in einer Session speichern mit 1GB größe?

        das hängt davon ab, ob Du PHP soviel Arbeitsspeicher zuweisen kannst. Das wird in der Praxis nicht möglich sein. Die Datei liegt serialisiert auf dem Permanentmedium und muss vollständig in den Arbeitsspeicher passen, um sie deserialisieren zu können.

        Frage 2: Gibt es noch eine andere Möglichkeit?

        Ich weiss nicht, ob man eine XML-Datei "zeilenweise" auswerten kann, vermutlich geht das ja auch nur blockweise und man muss vermutlich mit Tricks arbeiten, die PHP nur mit Umsteiger unterstützt. Man muss ve rmutlich erst einen Index aus den Haupt-Nodes erzeugen, um die Datei dann zerlegen zu können in auswertbare Portionen.

        Zur Fortschrittsanzeige:
        Generell könntest Du aber den Fortschritt der Operation in einer Datei ablegen, die dann vom Client per XHR regelmäßig gepollt wird und angezeigt...

        Liebe Grüße
        Tom S.

        --
        Es gibt nichts Gutes, außer man tut es
        Andersdenkende waren noch nie beliebt, aber meistens diejenigen, die die Freiheit vorangebracht haben.
  2. Hello,

    wie sieht denn die Struktur der XML-Datei aus?
    Ist die regelmäßig?

    Wieso löschst Du Artikel aus der DB-Tabelle, wenn Du doch Datensätze importieren willst?

    Hast Du exec()-Rechte auf dem Host, auf dem PHP ausgeführt wird? Ist es ei Linux- oder ein Windoes-Host?

    Hast Du Einfluss auf die Datenbank? Darfst Du Tabellen anlegen und löschen.

    Liebe Grüße
    Tom S.

    --
    Es gibt nichts Gutes, außer man tut es
    Andersdenkende waren noch nie beliebt, aber meistens diejenigen, die die Freiheit vorangebracht haben.
    1. Die XML kann sich nach eine Zeit verändern, wenn vom Shop neue Produkte hinzugefügt worden sind. Deswegen lösche ich als erstes alle Produkte. Ich könnte auch etwas besser vorgehen und die neue XML vergleichen mit der Datenbank und nur die neuen Produkte hinzufügen und die nicht mehr vorhanden sind löschen wäre eventuell etwas besser.

      Habe kompletten Zugriff. Webseite liegt auf mein Linux-Root-Server.

      1. Hello,

        leider beantwortest Du die Frage nach der Struktur der XML-Datei und der der DB-Tabelle nicht. Sonst hättest Du vermutlich schon eine (fast) fertige Lösung von uns…

        Die XML kann sich nach eine Zeit verändern, wenn vom Shop neue Produkte hinzugefügt worden sind. Deswegen lösche ich als erstes alle Produkte. Ich könnte auch etwas besser vorgehen und die neue XML vergleichen mit der Datenbank und nur die neuen Produkte hinzufügen und die nicht mehr vorhanden sind löschen wäre eventuell etwas besser.

        Habe kompletten Zugriff. Webseite liegt auf mein Linux-Root-Server.

        Liebe Grüße
        Tom S.

        --
        Es gibt nichts Gutes, außer man tut es
        Andersdenkende waren noch nie beliebt, aber meistens diejenigen, die die Freiheit vorangebracht haben.
  3. habe riesige XML Dateien teilweise 900MB die will ich gerne Parsen und in der Datenbank speichern

    Bei einer solchen Datenmenge ist das totaler Unfug. Warum dieser unsinnige Umweg über XML? Ich kannte eine Firma die haben das genauso gemacht und bei diesem Verfahren täglich den Shop lahmgelegt und zwar komplett über mehrere Stunden. Selbst das Aktualisieren einzelner Produkte, neue Preisauszeichnung usw. dauerte viel zu lange, falls der Prozess überhaupt sauber durchlief.

    Und auch bei kleineren Datenmengen hast Du immer das Problem mit der Konsistenz: Wie willst Du prüfen, ob der Preis für jede Badehose stimmt? MfG