2016-04-07 3 views
0

Ich benutze Gpars, um eine 250M Zeile MySQL-Datenbanktabelle parallel zu verarbeiten. Ich erstelle 8 gpars threads, 8 unabhängige Datenbankverbindungen und teile die Daten so auf, dass jeder Thread unabhängig auf verschiedenen Reihen von Reihen operiert ... eine Art billiges MapReduce-Konzept. Im Kern ist die Logik wie folgt aus:Groovy App mit Gpars verlangsamt nach vielen Iterationen

withExistingPool(pool) 
    { 
    connection_array.collectParallel()   
    { 
     // Figure out which connection this thread can use. 
     // We use the index into the array to figure out 
     // which thread we are, and this tells us where to 
     // read data. 

     int i 
     for (i = 0; i < connection_array.size(); i++) 
      if (it == connection_array[i]) 
      break 

     // Each thread runs the same query, with LIMIT controlling 
     // the position of rows it will read...if we have 8 threads 
     // reading 40000 rows per call to this routine, each thread 
     // reads 5000 rows (thread-0 reads rows 0-4999, thread-1 reads 
     // 5000-9999 and so forth). 

     def startrow = lastrow + (i * MAX_ROWS) 
     def rows = it.rows("SELECT * ... LIMIT ($startrow, $MAX_ROWS)") 

     // Add our rows to the result set we will return to the caller 
     // (needs to be serialized since many threads can be here) 

     lock.lock() 
     if (!result) 
      result = rows 
     else 
      result += rows 
     lock.unlock() 
    } 
} 

Der Code funktioniert zunächst groß, mich mehr als 10.000 Zeilen pro Sekunde geben, wenn es beginnt. Aber nach ein paar Millionen Zeilen beginnt es langsamer zu werden. Bis wir 25 Millionen Zeilen anstatt 10.000 Zeilen pro Sekunde haben, bekommen wir nur 1.000 Zeilen pro Sekunde. Wenn wir die App beenden und von dem Punkt aus starten, an dem wir aufgehört haben, geht sie für eine Weile wieder auf 10K Zeilen pro Sekunde zurück, aber sie wird immer langsamer, wenn die Verarbeitung fortgesetzt wird.

Es ist ausreichend Prozessorleistung verfügbar - dies ist ein 8-Wege-System und die Datenbank ist über ein Netzwerk, so dass es ziemlich viel Wartezeit gibt, egal was passiert. Die Prozessoren laufen im Allgemeinen nicht mehr als 25-30% beschäftigt, während dies ausgeführt wird. Scheint auch keine Speicherlecks zu sein - wir überwachen Speicherstatistiken und sehen keine Änderung, sobald die Verarbeitung läuft. Der MySQL-Server scheint nicht gestresst zu sein (er läuft anfangs zu etwa 30% ausgelastet und sinkt, wenn die App langsamer wird).

Gibt es irgendwelche Tricks, die dazu beitragen können, dass diese Art von Dingen bei einer großen Anzahl von Iterationen konsistenter ausgeführt wird?

+0

Es ist wahrscheinlich, weil Sie ständig die Größe der Ergebnisliste ändern. Haben Sie versucht, eine Anfangsgröße für das Ergebnis anzugeben? Sie zeigen nicht, wie/wo es initialisiert wird –

+0

Sie können wahrscheinlich auch '(0 ..

+0

@ tim - danke für die Vorschläge ... Ihr erster Kommentar ist ein guter - da wir wissen, wie viele Zeilen abgerufen werden, können wir das Ergebnis vorab zuordnen und als Parameter übergeben, anstatt es jedes Mal dynamisch zu erstellen. Dies half der Performance um fast 5%.Der zweite Vorschlag erwies sich als etwas langsamer als unser ursprünglicher Ansatz - ich denke, es dauert nicht lange, ein 8-Item-Array zu durchsuchen. Und leider existiert das ursprüngliche Problem immer noch ... die Routine wird immer langsamer, je mehr Datensätze sie verarbeitet. –

Antwort

0

Okay, wir denken, das Problem gefunden - es scheint wie es mit dem Öffnen der JDBC-Verbindung auf einem anderen Thread zu tun hat als wo es verwendet wird. Wenn Sie die Verbindung zu dem Thread, auf dem sie verwendet wird, zuerst öffnen und dann sicherstellen, dass NUR dieser Thread auf diese Verbindung zugreift, ist das Leistungsproblem verschwunden.

Wir haben auch die Logik ein wenig überarbeitet, um einen Cursor-basierten Ansatz anstelle von mehreren Abfragen mit LIMIT zu verwenden. Es gab Berichte, dass LIMIT mit high start_row langsam sein könnte, aber wir haben keinen großen Unterschied gesehen, wenn wir diese Änderung vorgenommen haben (Cursor waren schneller, aber die Leistung wurde immer noch verschlechtert, wenn Zeilen verarbeitet wurden).

Trotzdem, zwischen diesen und einigen der von tim_yates vorgeschlagenen Änderungen laufen wir gut 30% schneller als zuvor - und jetzt ist es konstant schnell, egal wie viele Zeilen wir verarbeiten.

+0

Vergiss nicht das Sammeln/jedes Ding in meinem obigen Kommentar –

0

LIMIT und OFFSET sind nicht so effizient, wie die meisten Menschen möchten.

Wenn Sie LIMIT 1000,20 tun, werden 1000 Zeilen gelesen, aber übersprungen, dann werden 20 Zeilen gelesen und zugestellt. Das heißt, wenn die OFFSET wächst, wird die Abfrage langsamer.

Die Technik "zu beheben" ist, "sich zu erinnern, wo Sie aufgehört haben". Dies ist besonders einfach mit einem Primärschlüssel AUTO_INCREMENT, aber es kann mit jedem Schlüssel PRIMARY KEY oder UNIQUE getan werden.

Dies ist discussed further in my "Pagination" blog. Es zielt auf "Weiter" -Schaltflächen auf einer Webseite ab, so dass einige der Diskussionen ignoriert werden können.

Verwandte Themen