Kürzlich kam ein Kunde auf uns zu, dem das Laufzeitzeitverhalten für eine Schnitsttelle nicht gefiel:
Ausgangsituation
Über einen Webservice werden täglich in einem Teilschritt rund 270.000 Datensäze abgerufen. Diese müssen zum Teil umformatiert werden und das Ergebnis (die Anzahl der Zeilen bleibt dabei unverändert) in eine Datenbanktabelle (MSSQL) eingefügt werden.
Erster Modifikationsversuch
Zunächst lag Nahe, dass das Schreiben mit Einzel-Inserts zu den langen Laufzeiten führt. Also wurde VB.net-Code derart modifiziert, dass immer 10.000 Zeilen zunächst in eine lokale DataTable im Arbeitsspeicher geschrieben wurden und diese dann per Bulk-Insert in die Zieltabelle eingefügt wurden.
Die Verarbeitung war zunächst “gefühlt” schneller, wurde dann aber zusehends langsamer. Also musste noch ein weiterer Grund für den Performanceeinbruch vorliegen.
Die Lösung
Zur Verarbeitung der Daten wurde der XML-Node als String verarbeitet. Dabei mussten Bestandteile der Daten per replace für die Aufbereitung der Zieldatenbank geändert bzw. neu formatiert werden.
Die Lösung bestand darin die Verarbeitung von Strings zu verhindern und die Daten, soweit es geht im StringBuilder zu verarbeiten. Erst am Ende ist eine Konvertierung zu einem String notwendig, um die Daten per split() in ein Array zu übernehmen.
Außerdem wurde die For…Next-Schleife derart modifziert, dass nicht erst der jeweilige XML-Node gesucht werden muss. Mit For..Each werden einfach der Reihe nach alle Knoten durchlaufen.
Im Gegensatz zum BulkInsert, gab es nun keinen Geschwindigkeitsverlust mehr. Die Verarbeitungszeit konnte damit von ursprünglich mehr als 40min auf etwa 4min gesenkt werden – und das noch ohne BulkInsert. Den Einfluss von BulkInsert testen wir aber auch noch in den kommenden Tagen.
' Vorher:
For i = 0 To colmax - 1
result = XmlDecodeData(xmldoc.GetElementsByTagName("data:Result").Item(i).InnerXml.ToString)
' Umstellung Trennzeichen damit Semikolons in Textfeldern nicht stören
result = Replace(result, """;""", """||""")
cols = Split(result, "||")
' ...weiterer Code
Next
Im ursprünglichen Code, gab es durch die String-Variablen gleich mehrere Stellen, die in StringBuilder-Funktionen konvertiert werden mussten. Die XmlDecodeData wurde ebenfalls in eine StringBuilder-fähige Version umgebaut:
' Nachher:
Dim result As StringBuilder = New Stringuilder("")
Dim cols() As String
For Each XmlRow In xmldoc.GetElementsByTagName("data:Result")
result.Clear()
result.Append(XmlRow.InnerXml.Replace(""";""", """||""")
result = XmlDecodeDataSB(result)
cols = Split(result.ToString(), "||")
'... weiterer Code
Next
Weitere Vorschläge Willkommen! Update folgt..