.
Anmeldung | Registrieren | Hilfe

.NET-Blogs Archiv Juni 2014

Save the Date – ALM Days 2015

27.06.2014 13:42:26 | Christian Binder

Die ALM Days 2015 werden wieder in Düsseldorf am 11. & 12. März 2015 stattfinden, also am besten schon mal vormerken.

image

Auch diesmal werden wir wieder Top Speaker und KnowHow aus der Microsoft Produktgruppe und der ALM Community vor Ort haben. Die nächste Generation von Visual Studio ALM wird dann auch in den Startlöchern stehen und eine ganze Reihe neuer Entwicklungen mitbringen, das Timing passt also. Zudem werden wir im top ALM Themen aufgreifen und diskutieren wie DevOps, Application Telemetry, ALM im Heterogenen- und im Cloud Umfeld, Agilität in größeren Organisationen und Agile Transformation von Organisationen auch im Kontext von formalen Workloads. Um mögliche Schwerpunkte besser zu priorisieren, möchten wir dieses Jahr gerne Themenwünsche aus der Community sammeln und einfließen lassen, um die ALM Days noch relevanter zu gestallten. Feedback und Themen könnt Ihr uns hier formlos zukommen lassen.  Zu guter Letzt wollen wir mehr Möglichkeiten zur Interaktivität anbieten, um den Austausch noch mehr in Vordergrund zu stellen. Wie genau werde ich in einem der späteren Posts beschreiben. Stay tuned.

Chris

Was tun?

25.06.2014 13:07:43 | Mathias Gronau

Im März 2008, also vor mehr als sechs Jahren, habe ich begonnen, diesen Blog zu schreiben. Das ist eine lange Zeit, in der sich viel getan hat Als ich mit dem Schreiben dieses Blogs begonnen habe, war der Tablet PC zwar bereits längere Zeit auf dem Markt, wurde aber kaum beachtet. Kaum ein Mensch konnte sich vorstellen, wie ein Tablet PC ihn bei der täglichen Arbeit unterstützen kann. Das kann ich durchaus auch nachvollziehen, denn auch die meisten Entwickler wussten nicht, wie sie Software für den Tablet PC erstellen sollten. Es erschien alles furchtbar schwierig – für Visual Studio musste ein zusätzliches SDK geladen werden und wie die Benutzeroberfläche aussehen soll war unklar. Es gab etliche Ideen, von denen sich aber keine wirklich durchsetzen konnte. Überhaupt unterschied sich das ursprüngliche Konzept des Tablet PCs deutlich von den heutigen Tablets. Die ersten Tablet PCs waren größer, schwerer und dicker als die heutigen Tablets. Außerdem verfügten sie in der Regel über mehr Arbeitsspeicher und permanenten Speicher als die heute erscheinenden Tablets mit Windows 8.1. Die entscheidenden Unterschiede befanden sich aber unter der Haube. So schloss Microsofts erste Tablet PC-Spezifikation die Bedienung von Touch ausdrücklich aus. Die Geräte sollten ausschließlich durch den Stylus gesteuert werden. Zusätzlich hatte sie noch einige Hardwaretasten, die Funktionalitäten boten, auf die mit dem Stylus nicht so einfach zu erreichen waren, beispielsweise die Tastenkombination Ctrl+Alt+Del. Daraus wird bereits klar, dass die ersten Tablet PCs in erster Linie Maschinen für die Arbeit waren, sinnvoll überall dort einzusetzen, wo der Laptop nicht mobil genug war. Damals war es noch wirklich spannend, zu recherchieren welche Unternehmen Tablet PCs einsetzen und welche Aufgaben sie damit erledigten. Außerdem war es interessant, zu sehen, wie die unterschiedlichen Entwickler die Oberflächen für die Stiftbedienung gestaltet haben. Leider hat in dieser Zeit Microsofts Marketing zumindest meiner Meinung nach komplett versagt, so dass die meisten Nutzer nicht wussten, dass es de Tablet PC überhaupt gab und diejenigen, die ihn kannten, wussten mit ihm meist nicht viel anzufangen. Dann kam Apple mit dem iPad. Vor dem Launch des ersten iPad waren bereits viele Gerüchte im Umlauf, die durchaus auch mich erreicht haben und wenn es sich um ein Tablet mit Mac OS gehandelt hätte, wäre ich sicher bei den ersten gewesen, die sich so ein Gerät zugelegt hätten. Tatsächlich lief und läuft auf dem iPad aber kein vollwertiges Betriebssystem, sondern ein abgespecktes Mobill-OS. Damit ist das iPad im Vergleich zum Tablet PC ein minderwertiges Gerät. Trotzdem rief ausgerechnet dieser aufgehübschte PDA ein breites Interesse am Formfaktor Tablet hervor, so sehr, dass sogar Microsoft in der weiteren Entwicklung sich mehr und mehr an Apples Vorgaben zu orientieren scheint. Und heute? Die Tablet PCs sind schicker geworden – oder sollte ich “cooler” sagen? Sie sind flacher und leichter, bieten aber trotzdem in etwa die gleiche Akkulaufzeit wie die ersten Tablet PCs. Zur Stiftbedienung des Tablet PC ist noch Touch gekommen. Nun spricht selbstverständlich nichts gegen Touch als solches, aber leider meint Microsoft, dass es erforderlich ist, Touch und Stiftbedienung gleichzeitig anbieten […]

Warum Sie bei der Softwareentwicklung immer mit realistischen Testdaten entwickeln sollten

24.06.2014 18:30:12 | Andre Kraemer

Kürzlich habe ich für einen Kunden ein Code- und Architekturreview durchgeführt. Es handelte sich um eine Web-Anwendung, die als Software as a Service (SaaS) Lösung vertrieben werden soll. Die Lösung befindet sich aktuell in einer Beta Phase und wird bereits von ca. 50 ausgewählten Kunden genutzt. Kurz vor Start der Beta Phase verließ der ursprüngliche Softwarearchitekt das Unternehmen. Sein Nachfolger hatte den subjektiven Eindruck, dass die Architektur an einigen Stellen nicht optimal ist. Nun ist es in der Softwareentwicklung häufig so, dass man alles, was man nicht selbst entwickelt hat, als nicht optimal ansieht. Daher wollte er sich eine zweite, unabhängige und vor allem objektive, Meinung einholen, ehe er größere Umbaumaßnahmen auf der Zielgeraden durchführt.

Auf den ersten Blick machten sowohl die Architektur als auch der Code einen sehr sauberen Eindruck. Innerhalb der Web-Anwendung gab es zwar einige Unschönheiten bei der Nutzung des Entity Framework, über die ich zu einem späteren Zeitpunkt noch einmal schreiben werde, im Großen und Ganzen sah der Code jedoch in Ordnung aus. Auch eine statische Code Analyse mit NDepend zeigte keine groben Schnitzer.

Stutzig machte mich jedoch der Code, der außerhalb der Web-Anwendung läuft. Neben der Web-Anwendung existiert nämlich noch ein zweites Programm, welches regelmäßig Daten aus mehreren Fremdsystemen liest, diese mit der eigenen Datenbasis vergleicht und, abhängig vom Ergebnis des Vergleichs, neue Daten einfügt, die später von der Web-Anwendung dargestellt werden.

Ausgangslage

Sehr vereinfacht kann man sich folgendes Modell und folgende Abbildung des Modells in der Datenbank vorstellen:

image image

Wir haben also Kunden, die über Bestellungen verfügen, die wiederum Bestellpositionen haben, welche auf Produkte verweisen. Außerdem gibt es sogenannte Notification Objekte, die zu bestimmten Ereignissen geschrieben werden. Das tatsächliche Modell meines Kundens war natürlich weitaus umfangreicher und auch in einer anderen Geschäftsdomäne. Zu Erläuterungszwecken reicht diese Vereinfachung jedoch vollkommen aus.

Der zweifelhafte Quellcode sah (stark vereinfacht!) wie folgt aus:

var lastRun = DateTime.Now.AddDays(-7); // lastRun wurde natürlich aus der DB geladen
var sw = new Stopwatch();
using (var db = new Db())
{
    sw.Start();
    // Hier wurden eigentlich Daten aus einer anderen Datenquelle geladen
    // Diese Daten wurden dann in den Vergleich einbezogen. Zu Demozwecken
    // soll jedoch diese Vereinfachung genügen
    foreach (var order in db.Orders)
        .Where(o => o.OrderState == OrderState.Shipped
            && o.OrderDate > lastRun).ToList())
    {
        db.Notifications.Add(new Notification { Message = string.Format("Order {0} Shipped", order.Id), Order = order });
    db.SaveChanges();
    }
    sw.Stop();

}
Console.WriteLine("Processing took {0} ms", sw.ElapsedMilliseconds);

Es wurden also Daten aus einem Drittsystem abgerufen, mit allen Daten, die sich seit dem letzten Lauf verändert hatten, verglichen und - je nach Ergebnis - wurden neue Daten in der Anwendung erzeugt (vereinfacht gesagt). Der Architekt hatte zwar bemerkt, dass dieser Code nicht optimal lief, allerdings konnte er die Auswirkungen nicht vollständig einschätzen und, da er zuvor noch nie mit dem Entity Framework gearbeitet hatte, wusste er auch nicht, wie er das Problem lösen könnte.

Haben wir ein Problem?

Im ersten Schritt wollte ich dem Kunden zunächst darlegen, dass er ein Problem hat, und wie groß dies später werden wird.

Während der Entwicklung hatte das Entwicklungsteam stets mit manuell erzeugten Testdaten gearbeitet. Es waren einige 100 Datensätze, die aufwändig durch die Entwickler eingepflegt wurden. Mit dieser Datenmenge lief das System absolut zufriedenstellend. Die ersten Probleme zeigten sich, als die Software von den ersten Beta Anwendern genutzt wurden. Bereits bei 50 Kunden mit 40 Transaktionen (neuen Bestellungen) pro Verarbeitungslauf wurde schnell klar, dass die Performance schon bei 50 Anwendern nicht zufriedenstellend ist.

Ein Gespräch mit dem Produktmanager ergab nun, dass man das klare Ziel hat, 10.000 Kunden mit jeweils 100 Transaktionen pro Lauf bedienen zu können. Um die Daten möglichst aktuell zu halten, sollte der Verarbeitungslauf vier mal pro Stunde laufen. Dies bedeutet, dass das System in der Lage sein muss, 1.000.000 Datensätze in weniger als 15 Minuten zu verarbeiten.

Um nicht weiterhin nur vermuten zu können, ob das System diese Anforderungen erfüllen kann oder nicht, entschieden wir uns dazu, die Entwicklungsdatenbank mit einer realistischen Anzahl von Testdaten zu füllen. Bereits bei einem einfachen Schema wie unserem, in dem es mehrere beteiligte Tabellen inklusive Beziehungen zwischen den Tabellen gibt, ist es jedoch gar nicht so einfach, realistische Testdaten zu generieren.

Natürlich hätten wir mit einem kleinen Programm Schritt für Schritt alle Tabellen mit hochgezählten Dummy Daten füllen können, allerdings hätte es zum einen eine Weile gedauert, dieses Programm zu schreiben, zum anderen wären die Daten auch relativ monoton gewesen.

Eine andere Alternative wäre es gewesen, einen Dump der Live-Datenbank einzuspielen und dort Daten zu duplizieren. In diesem Fall hätten jedoch alle Daten aus Datenschutzgründen anonymisiert werden müssen, was auch nur schwer umsetzbar gewesen wäre.

Deshalb entschieden wir uns dafür, die Daten mit dem redgate SQL Data Generator zu erzeugen. Das schöne am SQL Data Generator ist nicht nur, dass er realistische Testdaten erzeugen kann, wie man in diesem Screenshot sieht:

image

sondern dass er auch wunderbar mit Fremdschlüsseln umgehen kann, und somit in kürzester Zeit realistische und vor allem konsistente Testdaten in großer Menge erzeugen kann:

image

Nachdem wir nun also eine realistische Testdatenbank hatten, ließen wir das Programm erneut laufen. Dieser Durchlauf dauerte mehr als 9 h! Die durch das Produktmanagement geforderte Verarbeitungszeit von 15 Minuten wurde also um mehr als das 36-fache überschritten. Wie in vielen anderen Entwicklungsprojekten auch, ist dieses Problem während der Entwicklung nie aufgefallen, da keine realistischen Testdaten vorlagen und keine entwicklungsbegleitenden Performancetests durchgeführt wurden.

Wie können wir das Problem lösen?

Nachdem nun klar war, dass ein geschäftskritisches Problem besteht, musste natürlich eine Lösung her. Schnell wurden die ersten Ansätze unter den Entwicklern diskutiert. Der Vorschlag mit der meisten Zustimmung bestand aus einer Kombination aus Multiprocessing, zeitweise inkonsistenten Daten und Event Sourcing. Das vom Entwickler angezeichnete Architekturbild dazu sah auch wirklich sehr gut aus, allerdings schreckten mich zwei Dinge ab:

  1. Die Dauer für die Implementierung dieser Lösung  schätzte das Entwicklungsteam auf 3 Monate
  2. Ob diese Änderung wirklich erfolgreich sein würde, wurde nur vermutet. Bisher hatte niemand nachgemessen, wo genau innerhalb der Verarbeitungscodes der Flaschenhals war und ob der neue Ansatz überhaupt an der richtigen Stelle ansetzt

Also entschieden wir uns dazu, einen Gang zurück zu schalten und erst einmal mit einem Performance Profiler zu messen, wo das Problem wirklich liegt.

image

Wie der Screenshot des ANTS Performance Profiler zeigt, wird die meiste Zeit von der Methode SaveChanges verbraucht, dicht gefolgt von der Methode Add des DbSet. Beide Methoden werden in diesem Beispiel mit 10.000 Quelldatensätzen 4.777 Mal aufgerufen, nämlich für jeden Datensatz, der neu hinzugefügt werden soll.

Seit der Version 6 des Entity Framework ist es jedoch gar nicht mehr notwendig, jedes Objekt einem DbSet einzeln hinzuzufügen. Stattdessen kann über AddRange eine komplette Liste hinzugefügt werden, wie das nachfolgende Listing zeigt. Außerdem muss SaveChanges auch nicht jedes Mal aufgerufen werden, sondern nur einmalig bzw. häppchenweise.

var lastRun = DateTime.Now.AddDays(-7); // lastRun wurde natürlich aus der DB geladen
var sw = new Stopwatch();
using (var db = new Db())
{
    sw.Start();
    var notifications = new List();
    // Hier wurden eigentlich Daten aus einer anderen Datenquelle geladen
    // Diese Daten wurden dann in den Vergleich einbezogen. Zu Demozwecken
    // soll jedoch diese Vereinfachung genügen
    foreach (var order in db.Orders)
        .Where(o => o.OrderState == OrderState.Shipped
            && o.OrderDate > lastRun).ToList())
    {
        notifications.Add(new Notification { Message = string.Format("Order {0} Shipped", order.Id), Order = order });
    }
    db.Notifications.AddRange(notifications);
    db.SaveChanges();
    sw.Stop();

}
Console.WriteLine("Processing took {0} ms", sw.ElapsedMilliseconds);

Durch diese Code Änderung konnte die Verarbeitungszeit von 9 Stunden auf 6 Minuten reduziert werden.

Auch ein erneuter Blick auf redgates ANTS Performance Profiler zeigt, dass AddRange und SaveChanges nun nur noch eine untergeordnete Rolle spielen.

image

Fazit

Natürlich ist der vorliegende Quellcode noch nicht optimal. Den Entity Framework Kontext mit so einer großen Datenmenge zu befüllen und nur einmalig SaveChanges aufzurufen, birgt immer das Risiko einer Out-Of-Memory Exception. Daher sollte besser häppchenweise gespeichert werden. Noch besser wäre es natürlich die Massendaten über ein SQLBulkCopy einzufügen.

Mit der Erweiterung EntityFramework.BulkInsert geht dies relativ einfach, wie das nachfolgende Listing zeigt. Der Code läuft für 1.000.000 Datensätze übrigens innerhalb von 31 Sekunden.

var lastRun = DateTime.Now.AddDays(-7); // lastRun wurde natürlich aus der DB geladen
var sw = new Stopwatch();
using (var db = new Db())
{
    using (var transactionScope = new TransactionScope())
    {
        db.Configuration.AutoDetectChangesEnabled = false;

        sw.Start();
        var notifications = new List();
        foreach (var order in db.Orders
            .Where(o => o.OrderState == OrderState.Shipped
                        && o.OrderDate > lastRun).ToList())
        {
            notifications.Add(new Notification
            {
                Message = string.Format("Order {0} Shipped", order.Id),
                Order = order
            });
        }
        db.BulkInsert(notifications);
        db.SaveChanges();
        transactionScope.Complete();
    }

    sw.Stop();

}
Console.WriteLine("Processing took {0} ms", sw.ElapsedMilliseconds);

Die genaue Lösung war mir für diesen Beitrag jedoch gar nicht so wichtig.

Stattdessen möchte ich folgende Punkte noch einmal hervorheben:

  1. Bereits während der Entwicklung sollte man darauf achten, mit realistischen Testdaten in einer ausreichenden Menge zu arbeiten. Ein Werkzeug wie der SQL Data Generator hilft hier ungemein.
  2. Wenn ein Performanceproblem erkannt wird, dann sollte man immer die zuerst die genaue Ursache mit einem Profiler analysieren ehe man mit der vermuteten Lösung beginnt.

Warum Sie bei der Softwareentwicklung immer mit realistischen Testdaten entwickeln sollten

24.06.2014 17:30:12 | Andre Kraemer

Kürzlich habe ich für einen Kunden ein Code- und Architekturreview durchgeführt. Es handelte sich um eine Web-Anwendung, die als Software as a Service (SaaS) Lösung vertrieben werden soll. Die Lösung befindet sich aktuell in einer Beta Phase und wird bereits von ca. 50 ausgewählten Kunden genutzt. Kurz vor Start der Beta Phase verließ der ursprüngliche Softwarearchitekt das Unternehmen. Sein Nachfolger hatte den subjektiven Eindruck, dass die Architektur...

Warum Sie bei der Softwareentwicklung immer mit realistischen Testdaten entwickeln sollten

24.06.2014 11:16:00 | Andre Kraemer

Kürzlich habe ich für einen Kunden ein Code- und Architekturreview durchgeführt. Es handelte sich um eine Web-Anwendung, die als Software as a Service (SaaS) Lösung vertrieben werden soll. Die Lösung befindet sich aktuell in einer Beta Phase und wird bereits von ca. 50 ausgewählten Kunden genutzt. Kurz vor Start der Beta Phase verließ der ursprüngliche Softwarearchitekt das Unternehmen. Sein Nachfolger hatte den subjektiven Eindruck, dass die Architektur an einigen Stellen nicht optimal ist. Nun ist es in der Softwareentwicklung häufig so, dass man alles, was man nicht selbst entwickelt hat, als nicht optimal ansieht. Daher wollte er sich eine zweite, unabhängige und vor allem objektive, Meinung einholen, ehe er größere Umbaumaßnahmen auf der Zielgeraden durchführt.

Auf den ersten Blick machten sowohl die Architektur als auch der Code einen sehr sauberen Eindruck. Innerhalb der Web-Anwendung gab es zwar einige Unschönheiten bei der Nutzung des Entity Framework, über die ich zu einem späteren Zeitpunkt noch einmal schreiben werde, im Großen und Ganzen sah der Code jedoch in Ordnung aus. Auch eine statische Code Analyse mit NDepend zeigte keine groben Schnitzer.

Stutzig machte mich jedoch der Code, der außerhalb der Web-Anwendung läuft. Neben der Web-Anwendung existiert nämlich noch ein zweites Programm, welches regelmäßig Daten aus mehreren Fremdsystemen liest, diese mit der eigenen Datenbasis vergleicht und, abhängig vom Ergebnis des Vergleichs, neue Daten einfügt, die später von der Web-Anwendung dargestellt werden.

Ausgangslage

Sehr vereinfacht kann man sich folgendes Modell und folgende Abbildung des Modells in der Datenbank vorstellen:

image image

Wir haben also Kunden, die über Bestellungen verfügen, die wiederum Bestellpositionen haben, welche auf Produkte verweisen. Außerdem gibt es sogenannte Notification Objekte, die zu bestimmten Ereignissen geschrieben werden. Das tatsächliche Modell meines Kundens war natürlich weitaus umfangreicher und auch in einer anderen Geschäftsdomäne. Zu Erläuterungszwecken reicht diese Vereinfachung jedoch vollkommen aus.

Der zweifelhafte Quellcode sah (stark vereinfacht!) wie folgt aus:


var lastRun = DateTime.Now.AddDays(-7); // lastRun wurde natürlich aus der DB geladen
var sw = new Stopwatch();
using (var db = new Db())
{
                
    sw.Start();
    // Hier wurden eigentlich Daten aus einer anderen Datenquelle geladen
    // Diese Daten wurden dann in den Vergleich einbezogen. Zu Demozwecken
    // soll jedoch diese Vereinfachung genügen
    foreach (var order in db.Orders)
        .Where(o => o.OrderState == OrderState.Shipped 
            && o.OrderDate > lastRun).ToList())
    {
        db.Notifications.Add(new Notification { Message = string.Format("Order {0} Shipped", order.Id), Order = order });
	db.SaveChanges();
    } 
    sw.Stop();

}
Console.WriteLine("Processing took {0} ms", sw.ElapsedMilliseconds);

Es wurden also Daten aus einem Drittsystem abgerufen, mit allen Daten, die sich seit dem letzten Lauf verändert hatten, verglichen und - je nach Ergebnis - wurden neue Daten in der Anwendung erzeugt (vereinfacht gesagt). Der Architekt hatte zwar bemerkt, dass dieser Code nicht optimal lief, allerdings konnte er die Auswirkungen nicht vollständig einschätzen und, da er zuvor noch nie mit dem Entity Framework gearbeitet hatte, wusste er auch nicht, wie er das Problem lösen könnte.

Haben wir ein Problem?

Im ersten Schritt wollte ich dem Kunden zunächst darlegen, dass er ein Problem hat, und wie groß dies später werden wird.

Während der Entwicklung hatte das Entwicklungsteam stets mit manuell erzeugten Testdaten gearbeitet. Es waren einige 100 Datensätze, die aufwändig durch die Entwickler eingepflegt wurden. Mit dieser Datenmenge lief das System absolut zufriedenstellend. Die ersten Probleme zeigten sich, als die Software von den ersten Beta Anwendern genutzt wurden. Bereits bei 50 Kunden mit 40 Transaktionen (neuen Bestellungen) pro Verarbeitungslauf wurde schnell klar, dass die Performance schon bei 50 Anwendern nicht zufriedenstellend ist.

Ein Gespräch mit dem Produktmanager ergab nun, dass man das klare Ziel hat, 10.000 Kunden mit jeweils 100 Transaktionen pro Lauf bedienen zu können. Um die Daten möglichst aktuell zu halten, sollte der Verarbeitungslauf vier mal pro Stunde laufen. Dies bedeutet, dass das System in der Lage sein muss, 1.000.000 Datensätze in weniger als 15 Minuten zu verarbeiten.

Um nicht weiterhin nur vermuten zu können, ob das System diese Anforderungen erfüllen kann oder nicht, entschieden wir uns dazu, die Entwicklungsdatenbank mit einer realistischen Anzahl von Testdaten zu füllen. Bereits bei einem einfachen Schema wie unserem, in dem es mehrere beteiligte Tabellen inklusive Beziehungen zwischen den Tabellen gibt, ist es jedoch gar nicht so einfach, realistische Testdaten zu generieren.

Natürlich hätten wir mit einem kleinen Programm Schritt für Schritt alle Tabellen mit hochgezählten Dummy Daten füllen können, allerdings hätte es zum einen eine Weile gedauert, dieses Programm zu schreiben, zum anderen wären die Daten auch relativ monoton gewesen.

Eine andere Alternative wäre es gewesen, einen Dump der Live-Datenbank einzuspielen und dort Daten zu duplizieren. In diesem Fall hätten jedoch alle Daten aus Datenschutzgründen anonymisiert werden müssen, was auch nur schwer umsetzbar gewesen wäre.

Deshalb entschieden wir uns dafür, die Daten mit dem redgate SQL Data Generator zu erzeugen. Das schöne am SQL Data Generator ist nicht nur, dass er realistische Testdaten erzeugen kann, wie man in diesem Screenshot sieht:

image

sondern dass er auch wunderbar mit Fremdschlüsseln umgehen kann, und somit in kürzester Zeit realistische und vor allem konsistente Testdaten in großer Menge erzeugen kann:

image

Nachdem wir nun also eine realistische Testdatenbank hatten, ließen wir das Programm erneut laufen. Dieser Durchlauf dauerte mehr als 9 h! Die durch das Produktmanagement geforderte Verarbeitungszeit von 15 Minuten wurde also um mehr als das 36-fache überschritten. Wie in vielen anderen Entwicklungsprojekten auch, ist dieses Problem während der Entwicklung nie aufgefallen, da keine realistischen Testdaten vorlagen und keine entwicklungsbegleitenden Performancetests durchgeführt wurden.

Wie können wir das Problem lösen?

Nachdem nun klar war, dass ein geschäftskritisches Problem besteht, musste natürlich eine Lösung her. Schnell wurden die ersten Ansätze unter den Entwicklern diskutiert. Der Vorschlag mit der meisten Zustimmung bestand aus einer Kombination aus Multiprocessing, zeitweise inkonsistenten Daten und Event Sourcing. Das vom Entwickler angezeichnete Architekturbild dazu sah auch wirklich sehr gut aus, allerdings schreckten mich zwei Dinge ab:

  1. Die Dauer für die Implementierung dieser Lösung  schätzte das Entwicklungsteam auf 3 Monate
  2. Ob diese Änderung wirklich erfolgreich sein würde, wurde nur vermutet. Bisher hatte niemand nachgemessen, wo genau innerhalb der Verarbeitungscodes der Flaschenhals war und ob der neue Ansatz überhaupt an der richtigen Stelle ansetzt

Also entschieden wir uns dazu, einen Gang zurück zu schalten und erst einmal mit einem Performance Profiler zu messen, wo das Problem wirklich liegt.

image

Wie der Screenshot des ANTS Performance Profiler zeigt, wird die meiste Zeit von der Methode SaveChanges verbraucht, dicht gefolgt von der Methode Add des DbSet. Beide Methoden werden in diesem Beispiel mit 10.000 Quelldatensätzen 4.777 Mal aufgerufen, nämlich für jeden Datensatz, der neu hinzugefügt werden soll.

Seit der Version 6 des Entity Framework ist es jedoch gar nicht mehr notwendig, jedes Objekt einem DbSet einzeln hinzuzufügen. Stattdessen kann über AddRange eine komplette Liste hinzugefügt werden, wie das nachfolgende Listing zeigt. Außerdem muss SaveChanges auch nicht jedes Mal aufgerufen werden, sondern nur einmalig bzw. häppchenweise.


var lastRun = DateTime.Now.AddDays(-7); // lastRun wurde natürlich aus der DB geladen
var sw = new Stopwatch();
using (var db = new Db())
{ 
    sw.Start();
    var notifications = new List();
    // Hier wurden eigentlich Daten aus einer anderen Datenquelle geladen
    // Diese Daten wurden dann in den Vergleich einbezogen. Zu Demozwecken
    // soll jedoch diese Vereinfachung genügen
    foreach (var order in db.Orders)
        .Where(o => o.OrderState == OrderState.Shipped 
            && o.OrderDate > lastRun).ToList())
    {
        notifications.Add(new Notification { Message = string.Format("Order {0} Shipped", order.Id), Order = order });
    }
    db.Notifications.AddRange(notifications);
    db.SaveChanges(); 
    sw.Stop();

}
Console.WriteLine("Processing took {0} ms", sw.ElapsedMilliseconds);

Durch diese Code Änderung konnte die Verarbeitungszeit von 9 Stunden auf 6 Minuten reduziert werden.

 

Auch ein erneuter Blick auf redgates ANTS Performance Profiler zeigt, dass AddRange und SaveChanges nun nur noch eine untergeordnete Rolle spielen.

 

image

 

Fazit

Natürlich ist der vorliegende Quellcode noch nicht optimal. Den Entity Framework Kontext mit so einer großen Datenmenge zu befüllen und nur einmalig SaveChanges aufzurufen, birgt immer das Risiko einer Out-Of-Memory Exception. Daher sollte besser häppchenweise gespeichert werden. Noch besser wäre es natürlich die Massendaten über ein SQLBulkCopy einzufügen.

Mit der Erweiterung EntityFramework.BulkInsert geht dies relativ einfach, wie das nachfolgende Listing zeigt. Der Code läuft für 1.000.000 Datensätze übrigens innerhalb von 31 Sekunden.


var lastRun = DateTime.Now.AddDays(-7); // lastRun wurde natürlich aus der DB geladen
var sw = new Stopwatch();
using (var db = new Db())
{
    using (var transactionScope = new TransactionScope())
    {
        db.Configuration.AutoDetectChangesEnabled = false;

        sw.Start();
        var notifications = new List();
        foreach (var order in db.Orders
            .Where(o => o.OrderState == OrderState.Shipped
                        && o.OrderDate > lastRun).ToList())
        {
            notifications.Add(new Notification
            {
                Message = string.Format("Order {0} Shipped", order.Id),
                Order = order
            });
        }
        db.BulkInsert(notifications);
        db.SaveChanges();
        transactionScope.Complete();
    }

    sw.Stop();

}
Console.WriteLine("Processing took {0} ms", sw.ElapsedMilliseconds);

Die genaue Lösung war mir für diesen Beitrag jedoch gar nicht so wichtig.

Stattdessen möchte ich folgende Punkte noch einmal hervorheben:

  1. Bereits während der Entwicklung sollte man darauf achten, mit realistischen Testdaten in einer ausreichenden Menge zu arbeiten. Ein Werkzeug wie der SQL Data Generator hilft hier ungemein.
  2. Wenn ein Performanceproblem erkannt wird, dann sollte man immer die zuerst die genaue Ursache mit einem Profiler analysieren ehe man mit der vermuteten Lösung beginnt.

HTTPS-Redirect - aber konfigurierbar bitte

12.06.2014 23:06:00 | Martin Hey

Manchmal möchte man erzwingen, dass komplette Seiten oder Subdomains - oder um es anders zu formulieren: ganze ASP.NET Webanwendungen - nur per HTTPS erreichbar sind. Real-World-Beispiele gibt es zuhauf: Banking-Seiten, Shops usw.

Eine Möglichkeit ist es, das Ganze fix im Code zu verdrahten. In ASP.NET gibt es ein Filter-Attribut, das genau diesen Redirect vornimmt - das RequireHttpsAttribute. Dieses kann man einfach über die Controller oder Actions schreiben oder eben für den hier betrachteten Fall als globalen Filter definieren.

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        // für die ganze Seite HTTPS erzwingen
        filters.Add(new RequireHttpsAttribute());
    }
}

Nun möchte man aber vielleicht nicht immer auf die HTTPS-Seite weiterleiten, sondern nur in bestimmten Fällen. Ich würde beispielsweise gern auf meiner Entwicklungsmaschine auf SSL und den Aufwand den man dafür betreiben muss verzichten. Und ich hätte es gern konfigurierbar. Eine andere Instanz der Anwendung braucht dieses strikte Verhalten vielleicht nicht.

Dafür gibt es auch was - nämlich die web.config. Unter dem Key system.webServer können Redirects definiert werden. Ein entsprechender Eintrag könnte beispielsweise wie folgt aussehen:
<system.webServer>
  <rewrite>
    <rules>
      <rule name="HTTP to HTTPS redirect" stopProcessing="true">
        <match url="^(.*)$"/>
        <conditions>
          <add input="{HTTPS}" pattern="^OFF$" ignoreCase="true" />
          <add input="{HTTP_HOST}" matchType="Pattern" pattern="^localhost(:\d+)?$" negate="true" />
          <add input="{HTTP_HOST}" matchType="Pattern" pattern="^127\.0\.0\.1(:\d+)?$" negate="true" />
        </conditions>
        <action type="Redirect" redirectType="Permanent" url="https://{HTTP_HOST}/{R:1}" />
      </rule>
    </rules>
  </rewrite>
</system.webServer>

Was wird hier gemacht? Wie der Name schon andeutet, wird ein permantener (also Statuscode 301) Redirect vorgenommen - allerdings mit ein paar Ausnahmen: Wenn der Host localhost oder 127.0.0.1 ggf. noch gefolgt von einem Port ist, wird auf den Redirect verzichtet, denn dann befinde ich mich entweder auf dem Webserver oder auf der Entwicklungsmaschine und greife darüber zu. Eigentlich ganz einfach - oder?

Windows Store App “…does not contain a static ‘Main’ method suitable for an entry point”

12.06.2014 12:40:24 | Hendrik Loesch

Ich arbeite mich gerade in Universal Apps ein und bin über einen dummen Fehler gestolpert. Da in einer Universal App der gemeinsame Code in die entsprechenden Projekte geklinkt wird, muss an einigen Stellen in den .cs Dateien über Compiler Anweisungen mitgeteilt werden, wenn Code nicht zu verwenden ist. Gerade bei der app.Xaml wird dies deutlich. […]

Nicht suchen sondern abfragen

10.06.2014 16:12:05 | Klaus Bock

… oder; teile und herrsche.
Teile und herrsche ist ein altbekanntes Paradigma, auch in der Informatik. Man bricht ein Problem solange rekursiv herunter bis es beherrschbar, oder eben berechenbar wird.

Für ein aktuelles Problem habe ich mich wieder auf dieses alte Paradigma besonnen. Ich stand vor der Aufgabe, eine große Anzahl von IPv4-Adressen effizient in einer Struktur zu speichern, die ein schnelles durchsuchen genauso zulässt wie ein einfaches hinzufügen neuer Einträge, ohne die Auflistung neu sortieren zu müssen.

Spätestens jetzt verabschiedete ich mich von dem Gedanken, die IP-Adressen als IPAddress Objekte zu speichern. Vielmehr reifte der Gedanke, die einzelnen Oktette der IP-Adressen als Segmente in einer Struktur abzulegen. Gerade IP-Adressen kann man in den verschiedensten Formen darstellen. Als Ganzzahl, als Zeichenfolge in der Notation 129.168.0.1, oder als byte-Array 192|168|0|1.
Mein Gedankengang war folgender: Wenn eine Struktur bereits eine IP-Adresse mit dem Wert 192.168.0.1 enthält und ich will eine weitere Adresse mit dem Wert 192.168.0.2 hinzufügen, sind die ersten drei Oktette ja bereits vorhanden und es müsste lediglich das vierte Oktett hinzugefügt werden. Das war der Knackpunkt meiner Überlegung.

Die logische Folge währe ein Baum, oder genauer: eine Reihe von Bäumen. Allerdings ohne gemeinsame Wurzel, da jedes mögliche erste Oktett (1,….,254) die Wurzel eines eigenen Baums, ohne Verbindung zu einem vorherigen oder folgenden Baum, darstellt.

Da die Höhe der Bäume immer gleich ist, habe ich mich für die Abbildung der Bäume als Nested Sets (verschachtelte Mengen) entschieden. Allerdings eine stark vereinfachte Variante, da ich die Teilmengengrenzen (links und rechts) nicht benötige. Das Einfügen eines Knotens oder Blattes erfolgt immer von der Wurzel ausgehend. Auf diese Weise ist jeder Pfad zu einem Knoten eindeutig.

Schema einer Struktur zum speichern von IPv4-Adressen

Wenn die Struktur, so unterteilt, als leere Struktur initialisiert wird, muss die Anzahl der ersten Oktette bereits fest mit 256 Objekten initialisiert werden. Eigentlich währen es 254, da 0 und 255 nicht das erste Oktett einer gültigen IPv4-Adresse sein können. Wenn das Array aber mit einer Größe von 256 initialisiert wird, stimmen die Indexpositionen wieder mit dem Wert des ersten Oktetts überein.
So initialisiert ist jede mögliche Position des ersten Oktetts mit null vorbelegt. Wird eine Adresse hinzugefügt, wird die Position des Oktetts (der Wert eines Oktetts entspricht dem Index seines Objekts) mit einer Instanz eines Objekts belegt, welches ein Oktett mit seiner untergeordneten Struktur darstellt. Ist am entsprechenden Index bereits eine Instanz vorhanden, wird weiter in die Tiefe vorgegangen. Diese Art des Einfügens ist sehr effektiv, da kein Pfad ermittelt werden muss, sondern jeder Wert eines der Oktette eine Indexposition in einer Teilmenge (eine Reihe von Objekten) darstellt.

Ähnlich effektiv verhält es sich mit Abfragen.
Die abzufragende IP-Adresse wird in ihre Oktette zerlegt. Beginnend mit dem ersten Oktett wird abgefragt, ob die Indexposition null zurückgibt. Wenn ja, wird hier abgebrochen und false, also nicht enthalten, zurückgemeldet. Wird eine Instanz gefunden, wird in dieser die Indexposition des zweiten Oktetts abgefragt, Auch hier wieder das selbe Spiel, bis alle 4 Ebenen durchlaufen sind, oder vorher abgebrochen wird.

Flussdiagram einer Abfrage in der IPv4Map Struktur

Die Abfrage einzelner Adressen ist ziemlich trivial, wie obiges Flussdiagramm zeigt.
Die Iteration über die Struktur ist nicht ganz so einfach. Wie in allen Strukturen die Leerräume enthalten können, müssen diese Leerräume beim Abruf eines folgendes Wertes überbrückt werden ohne in einer Fehler zu laufen. Ich habe mir hier mit einem speziellen Index beholfen, der vom Enumerator verwendet wird. Dieser Index, in Zusammenarbeit mit einer speziellen Contains Methode, löst das Problem der Leerräume recht einfach.
Die genannte Contains Methode gibt als Rückgabewert eine Ganzzahl zurück, die die Ebene benennt in der ein Leerraum erkannt wurde, oder 0 wenn ein Folgeobjekt bestimmt ist.
Der verwendete Index erstreckt sich ebenfalls über 4 Ebenen und inkrementiert die jeweilige Ebene, entsprechende des Rückgabewertes der Contains Methode.

Die Implementierung der Struktur in Code ist nicht sehr kompliziert. Folgend ein Diagramm der verwendeten Klassen:

Diagramm der verwendeten Klassen

Die Klassen SecondOctet, ThirdOctet und FourthOctet werden von der Klasse IPv4Map zum Aufbau der Struktur verwendet. Die Klasse IPv4MapIndex wird, wie bereits weiter oben beschrieben, vom Enumerator IPv4MapEnumerator benötigt. Der Enumerator ist als geschachtelter Typ implementiert, um Zugriff auf eine private Contains Methode zu erhalten,deren Rückgabewert im IPv4MapIndex verarbeitet wird.

Den Index kann man sich als Reihe mit 4 Ganzzahlen vorstellen, deren Position der Ebene in der Struktur entspricht. Initialisiert wird der Index mit 1|0|0|0 und ist somit vor dem ersten Element, dass minimal den Wert 1.0.0.1 haben kann, positioniert. Die erste Inkrementierung des Index erfolgt in der vierten Ebene. Er wird demnach auf 1|0|0|1 gesetzt. Meldet eine Abfrage mit 1.0.0.1 den Wert 2 zurück. Wird der Index in der zweiten Ebene erhöht und die dritte und vierte Ebene werden auf 0 gesetzt. Der Wert im Index beträgt jetzt 1|1|0|0. Angenommen bei der nächsten Abfrage wird der Wert 4 zurückgegeben, wird der Index in der vierten Ebene erhöht und hätte dann den Wert 1|1|0|1. Wird an der Indexposition 1 in der vierten Ebene ein Wert gefunden, meldet die Contains Methode 0 zurück und das erste Objekt würde vom Enumerator ausgegeben werden.
Bei der nächsten Inkrementierung wird wieder zuerst die vierte Ebene inkrementiert und der Vorgang wiederholt sich, bis mit 255|0|0|0 das Ende des Index erreicht ist.

Fazit:

Das hier vorgestellte Konzept mag dem ein oder anderen zu kompliziert erscheinen. Vor allem unter dem Gesichtspunkt der diversen, bereits im Framework enthaltenen Auflistungen. Beinahe alle Auflistungen im Framework arbeiten intern mit Arrays die dynamisch vergrößert werden. Da es keine echte Vergrößerung von Arrays gibt, wird das an dieser Stelle mit dem umkopieren der bestehenden in größere Arrays bewerkstelligt. Das umkopieren kostet natürlich Zeit.
Auch kostet die Sortierung der Auflistungen bei jedem hinzufügen eines neuen Objekts Zeit.

Natürlich sind die Auflistungen im Framework einfach und elegant zu verwenden, denn sie verbergen die Komplexität vor dem Entwickler. Auch sind die meisten Auflistungen ausreichend schnell, zumindest in vielen Standardsituationen. Hat man aber spezielle Anforderungen, ist eine spezielle Lösung auf lange Sicht oft die bessere Lösung.

Technorati-Tags: | |

Die nächste Generation .NET

06.06.2014 15:56:55 | Christian Binder

.NET ist lebendiger als je zuvor und das wurde auf der diesjährigen //build und TechEd Konferenz mit diversen Sessions unterstrichen. Die wichtigsten Themen haben Chris und Dariusz in ihrem Video zusammengefasst.

 

Trotzdem möchte ich in diesem Blog Artikel die interessantesten Ankündigungen rund um die nächste Generation von .NET zusammenfassen:

  • .NET Foundation
  • .NET Native
  • .NET Compiler Platform ("Roslyn")
  • Die nächste Generation des JIT Compilers "RyuJIT"
  • Visual Studio 14 CTP Platform Image auf Azure IaaS

Scott Guthrie hat auf der Build 2014 die .NET Foundation angekündigt. Im Zuge der Open-Source Legung größerer Teile des .NET Frameworks, soll sich die .NET Foundation um den Transfer und die Weiterentwicklung quelloffener .NET Technologien kümmern. Der .NET Foundation gehören neben Xamarin (Mono Framework), MS Open Technologies, sechs weitere Firmen an. Zum Start werden 24 .NET Open Source Projekte u. a. die neue Compiler Plattform ("Roslyn") sowie sechs Xamarin Bibliotheken wie z. B. MimeKit und MailKit unter der Schirmherrschaft der .NET Foundation stehen. APIs sollen künftig über NuGet schneller und auch auf neuen Platformen ausgeliefert werden. Portable Class Libraries helfen Entwicklern Code auf anderen Plattformen wiederzuverwenden (Universal Apps) und die Community soll aktiv an der Weiterentwicklung beteiligt werden. Wer sich hierüber genauer informieren möchte, empfehle ich einen Blick auf die Homepage der .NET Foundation zu werfen.

Bei .NET Native erzeugt der neue Compiler keine Intermediate Language mehr, sondern direkt Maschinencode für die jeweilige Prozessorarchitektur. Ebenso liegt das .NET Framework als optimierter Code (.NET Native Framework) mit einer minimalen Common Language Runtime (CLR) vor. Die von einer Anwendung benötigten Teile des Frameworks werden vom Compiler statisch in die zu erstellende App gelinkt. Dabei kommt der hoch optimierte C++ Compiler mit den gleichen Optimierungen zum Einsatz, die auch bei C++ Programmcode angewendet werden. Die .NET Native Preview kann schon heute für Windows Store Apps genutzt werden. Erste Tests der Produktgruppe mit den Top Windows Store Apps wie z. B. Wordament zeigen, dass mit .NET Native kompilierte Programme ca. 60% schneller starten und durchschnittlich 25% weniger Speicher benötigen. Download der .NET Native Developer Preview.

Noch ein paar Sätze zur neuen Compilerplattform Roslyn sowie dem neuen JiT Compiler RyuJIT. RyuJIT ist der neue 64-bit Compiler für .NET und soll vor allem Server Apps zu schnelleren Startzeiten verhelfen. Bisher wurde bei der Performance des 64-bit Compilers auf lang laufende Serverprozesse fokussiert. Der Code wird einmal JiT-compiled und läuft dann für eine lange Zeit. RyuJIT basiert auf der selben Codebasis wie die x86 JiT. Damit können künftige Innovationen im Compiler schneller umgesetzt werden und Kunden profitieren von mehr Konsistenz bei .NET Programmen auf x86 und x64 Plattformen (RyuJIT: The next-generation JIT compiler for .NET)

Roslyn ist die neue Complierplattform, die mit Visual Studio 14 Einzug halten wird und jetzt schon als Preview verfügbar ist. Die Idee von Roslyn ist, den Compiler als Dienst zu betrachten, der alle Schritte von der Quelltext Analyse bis zur Generierung von .NET Bytecode ausführt. Dabei ist jede Phase steuerbar, sodass man jederzeit Informationen abrufen kann. Diese stehen als .NET APIs zur Verfügung. Roslyn ist eine Reimplementierung der bestehenden C# und VB Compiler in C#. Vor allem wird auch Visual Studio von Roslyn profitieren, da z. B. Refactoring Features künftig einfacher und schneller implementiert werden können (.NET Complier Platform auf CodePlex).

Zu guter Letzt. Gestern hat die Produktgruppe ein fertig konfiguriertes Visual Studio 14 Image in der Azure Galerie zur Verfügung gestellt. Wer Lust hat die neuen Funktionen zu testen und uns Feedback zu geben - ohne großen Installationsaufwand - kann sofort loslegen. Details gibt’s im Microsoft ALM Blog.

Viel Spaß

Artur & Chris

Neue Website markusschmid.me

05.06.2014 00:54:00 | Jürgen Gutsch

Für Markus Schmid, einen sehr guten Bekannten aus Kreuzlingen und Mitglied des .NET-Stammtisch Konstanz-Kreuzlingen, habe ich in den letzten zwei Monaten eine Portfolio-Website erstellt. Markus hat seine Kamera auf seinen Reisen immer parat und hat ein Auge für extrem eindrückliche Szenen und Motive.

Bewundern kann man seine Bilder auf eben dieser Website unter markusschmid.me oder auf einer seiner Ausstellungen.

Was ist nun besonderes an seiner Website? Natürlich meine Lieblingstechnologie bestehend aus ASP.NET MVC, Web API und Windows Azure. Desweiteren ist es (meines Wissens) das einzige Projekt, indem der SimpleObjectStore mit dem Azure-Provider produktiv zum Einsatz kommt.

Markus hat die Möglichkeit die Bilder auf dem Azure Blob Storage zu laden, die Metadaten der Bilder im Azure Table Storage zu hinterlegen um seine Werke auf seiner Website zu präsentieren. Dabei werden die Bilder – bei Bedarf – serverseitig heruntergerechnet und zwischengespeichert.

Änderungen werden ganz normal per Git deployed. Commits in den master-Branch lösen den Deployment-Prozess aus und maximal 5 Minuten Später sind die Änderungen online.

Was mich bei diesem Projekt absolut begeistert hat, ist die sehr gute Performance mit der der Azure Blob Storage die Bilder ausliefert. Das richtigen Caching auf dem Server und die guten Cache-Einstellungen für den Client runden das ganze noch ab.

Auf dem Client kommen Twitters Bootstrap, die FancyBox, sowie Supersized (für die Hintergrundbilder) zum Einsatz.

Auch hier hat sich wieder mal gezeigt, dass die Arbeit mit .NET und der richtigen Umgebung tatsächlich enorm Spaß macht.

Vortrag bei den Testing Info Days von Microsoft

01.06.2014 10:30:58 | Hendrik Loesch

Dieses Jahr war ich für Microsoft als Sprecher auf den Testing Info Days unterwegs. Dies waren zwei Veranstaltungen im März, bei denen ich jeweils in München und Hamburg über Unit Testing mit Visual Studio gesprochen habe. Der entsprechende Mitschnitt dazu findet sich auf Channel 9 und kann nachfolgend auch angesehen werden. Leider gab es wohl […]

Regeln | Impressum