Laden...

Forenbeiträge von UndercoverDeveloper Ingesamt 16 Beiträge

26.03.2021 - 10:29 Uhr

Vielen dank für diese ausführliche Antwort, Abt 🙂 Da sind interessante Ansätze dabei. Wir müssen allerdings auch unsere spezifische Systemumgebung noch mit einbeziehen, diese hatte ich hier zugegebenermaßen nicht erwähnt. Wir können z.B nicht auf CosmosDB wechseln, weil es sich um eine Neuentwicklung einer Altanwendung handelt. Diese setzt bereits auf einen SQL Server und da hängt noch einiges mehr dran. Außerdem muss Alt und Neu datentechnisch kompatibel bleiben.

Meine Hauptsorge ist auch mehr der SQL Server und nicht die Skalierung der API an sich. Die API hosten wir in Azure und ist daher an sich schon skalierbar aber der SQL Server muss leider erstmal On-Premise bleiben. Jeder API Request erzeugt demnach Last auf dem SQL Server und das versuchte ich durch das Caching zu vermeiden.

Wir haben hier intern auch nochmal diskutiert und werden die Lösung von FZelle umsetzen. Also die Datenänderungen direkt über SignalR senden. So groß sind die Objekte nun auch nicht, wir haben das nochmal kalkuliert. Das macht den technischen Aufbau einfacher aber reduziert trotzdem die Anfragen an die API und damit auch an den SQL Server.

Es war jedenfalls sinnvoll das hier mal zu diskutieren und andere Ansätze zu hören. Wieder etwas dazu gelernt was es alles an Möglichkeiten gibt 🙂

24.03.2021 - 16:06 Uhr

Ja, da habe ich mich wohl falsch ausgedrückt. Skalierbarkeit ist hier der bessere Begriff, richtig 🙂 Die Lösung soll skalieren, sodass auch bei einer großen Anzahl von Benutzern die Antwortzeiten akzeptabel sind.

24.03.2021 - 12:22 Uhr

Danke euch erstmal. Ja man könnte das neue Response-Objekt natürlich auch direkt über SignalR senden. Der Payload sollte nur nicht größer als 32KB sein, weil es dann die Performance negativ beeinflusst ( https://github.com/SignalR/SignalR/issues/1205 )

Zudem gibt es noch den Kostenfaktor wenn man wie wir SignalR nicht selber hostet sondern Azure verwendet. Eine Message hat 2KB und Messages die größer sind werden aufgesplittet und als einzelne Nachricht abgerechnet:

Messages and connections in Azure SignalR Service.

Also das ganze Response-Objekt würde ich, zumindest in unserem konkreten Szenario, nicht über SignalR schicken.

Vielleicht mache ich mir auch gerade einen zu großen Kopf um Performance. Muss ich vielleicht mal testen wie weit das skaliert wenn man nur die Id schickt und jeder Client selbst die Daten von der API holt (ohne Cache). Auch wenn das dann viele Zugriffe auf die SQL-Datenbank bedeutet.

24.03.2021 - 10:38 Uhr

Hallo,

ich möchte gerne mal eine entworfene Software Architektur von euch bewerten lassen. Vielleicht hat jemand von euch ja noch eine bessere Idee oder zumindest Optimierungsvorschläge 🙂

Szenario:

Es ist eine Web-Anwendung mit einem REST-API-Backend. In dieser Webanwendung können in einer Tabelle Daten geändert werden (Inline-Editing). Sobald ein Benutzer Daten in dieser Tabelle ändert sollen auch alle anderen Benutzer die geänderten Daten sofort sehen. Datenänderungen in der Tabelle passieren sehr häufig, sodass pro Minute hunderte Requests an die API gesendet werden könnten. Die Lösung muss also schnell sein und soll den Server nicht unnötig belasten.

Lösungsweg:

Um alle anderen Clients zu benachrichtigen haben wir bereits eine Lösung, wir verwenden dazu Azure SignalR. Das Problem, welches es zu lösen gilt, ist wie die anderen Clients an die geänderten Daten kommen. Zwar könnte jeder Client einen einfachen GET-Request an die API schicken und die API berechnet für jeden GET-Request ein neues Response-Objekt aber wenn das hunderte Clients gleichzeitig machen wird es Probleme geben.

Lösungen welche ich bisher ausgeschlossen habe:

  • Der erste GET-Request einen Clients welcher die geänderten Daten von der API abruft führt dazu das die API das genierte Response-Objekt im Cache speichert. Diese Lösung wäre technisch am einfachsten umzusetzen, wird aber nicht funktionieren, weil sehr viele Clients gleichzeitig die Benachrichtigung über geänderte Daten erhalten. Alle Clients fragen parallel die geänderten Daten an der API an und daher gibt es keinen Request welcher zuvor den Cache generiert.

  • Der POST/PUT-Endpunkt, welcher die Daten in die Datenbank speichert, erzeugt auch gleichzeitig ein Response-Objekt und speichert es im Cache. Alle anderen Clients können dann direkt auf das Response-Objekt im Cache zugreifen. Diese Lösung habe ich auch verworfen, weil es zusätzliche Rechenzeit im POST/PUT - Endpunkt bedeutet und die Antwortzeiten dieser Endpunkte verlängert.

Aktuelle Lösung:

Ich habe eine Skizze meiner erdachten Lösung angehängt, ich hoffe es ist halbwegs verständlich gezeichnet 🙂 Im Prinzip geht es darum das, dass Response-Objekt vorberechnet wird bevor alle anderen Clients über die Datenänderung benachrichtigt werden. Somit können alle anderen Clients das neue Response-Objekt direkt aus dem Cache beziehen. Das wesentliche Merkmal ist aber, dass nicht der POST/PUT - Endpunkt das Response-Objekt selber erstellt, sondern das an einen Hintergrundprozess weiterreicht. Der POST/PUT-Endpunkt kann somit schnell Antworten während in einem separaten Prozess die Cache-Generierung und Benachrichtigung aller anderen Clients läuft.

Weitere Ideen

Hier noch ein paar Ideen, welche in die Skizze noch nicht eingearbeitet sind aber vielleicht Sinn machen.

  • Man könnte zwischen API und Hintergrundprozess noch eine MessageQueue setzten (Azure Storage Queue, Azure Service Bus, RabbitMQ etc.). Das würde die Zuverlässigkeit erhöhen, falls der Hintergrundprozess für ein paar Sekunden nicht erreichbar sein sollte. Ist der Hintergrundprozess erreichbar kann er die Queue abarbeiten. Bei Systemen mit vielen Komponenten ist immer die Gefahr das eine Komponenten ausfällt. Was haltet ihr davon?

Ich danke schonmal jedem der sich die Zeit nimmt sich das mal anzuschauen 🙂

05.11.2020 - 11:10 Uhr

Sind die 100.000 Datensätze für jeden benutzer verschieden?
Wie viele Benutzer?

Grundsätzlich sind sie gleich, die Datensätze sind in Kategorien / Projekten angeordnet. Also der Benutzer wählt aus, aus welchem Projekt er sich die Datensätze anzeigen lassen möchte. In dem einen Projekt können 5000 Datensätze sein, mal 10.000 und eben einzelne Extremfälle mit 100.000.

Das System sollte 50-100 Nutzer gleichzeitig handeln können.

Ich habe jetzt aber einfach mal ein Testprojekt erstellt indem eine Tabelle mit 30 Spalten angezeigt wird die man filtern und sortieren kann und bin positiv überrascht.

1.000000 - 5.000000 Datensätze:

Sehr schnell. Kaum Verzögerung beim filtern.

5.000000 - 20.000000 Datensätze:

Verzögerung aber noch halbwegs akzeptabel

Ab 25.000000 Datensätzen bekomme ich OutOfMemory - Fehler in Chrome. Aber okay, solch ein Szenario kalkulieren wir auch nicht. Ich habe auch nur mit Datensätzen im Millionenbereich getestet um wirklich mal die Grenzen zu ermitteln.

Dann sollten 100.000 oder auch mal 200.000 - 300.000 Datensätze kein Problem sein. Ich habe da vielleicht die modernen Browser und Javascript etwas unterschätzt. Mittlerweise sind die ja doch recht leistungsfähig. Wir switchen gerade von Desktop auf Webentwicklung und müssen so Sachen daher erstmal evaluieren.

Ich denke, wir werden dann erstmal den Weg gehen dem Client die Filterung und Sortierung zu überlassen. Das macht die ganze Logik im Backend wesentlich einfacher und kommt der Entwicklungszeit zu gute. Es erzeugt auch generell weniger API Requests.

Die serverseitige Lösung mit dem Caching behalten wir mal im Hinterkopf, falls die Anforderungen doch noch steigen.

05.11.2020 - 09:08 Uhr

@T-Virus

Danke dir.

Alles Client-seitig zu machen würde vieles vereinfachen. Ich habe nur die Sorge das es nicht skalierbar ist. Vielleicht sind es irgendwann 200.000 Datensätze oder mehr.

Zudem soll die Möglichkeit bestehen die Webanwendung auch auf einem Tablet oder Smartphone zu verwenden. Hier habe ich dann Client-seitig nochmal eine geringere Leistung zur Verfügung und die Datenmenge zwischen Client und Server spielt eine größere Rolle. Mobile Nutzung ist jetzt keine Top-Priorität aber ich darf es auch nicht komplett ausschließen.

Schwierig 😁 Ich glaube ich muss mal ein Testprojekt machen um zu sehen ab welcher Datenmenge man Client-seitig an Grenzen kommt.

04.11.2020 - 22:04 Uhr

Wenn ihr im SQL Server indexed views verwenden könnt dann braucht ihr gar keinen cache sondern könnt direkt auf die indexierten daten zugreifen. das ist eine besondere fähigkeit vom SQL server. ich halte es aber für fragwürdig wieso ein web client eine tabelle mit 100.000 daten anzeigen soll.

Danke ich schau mir das mit den Indexed views mal an.

Natürlich müssen nicht alle 100.000 Datensätze immer dem User angezeigt werden. Wahrscheinlich sind immer nur rund 100 relevant. Das ganze Dataset muss aber für den User filterbar sein.

Damit ich alle Daten filtern kann muss ich erstmal alle Datensätze generieren. Wobei das hier vielleicht auch wieder ein Sonderfall bei uns ist. Die Daten die wir dem Benutzer da anzeigen sind stark aufbereitet und liegen nicht so in dieser Form filterbar in der Datenbank vor wie der User sie am Ende sehen soll / filtern können soll.

Das initiale Generieren dieser 100.000 Datensätze durch die Stored Procedure dauert vielleicht etwas mehr als 30 Sekunden. Das kann ich dem Nutzer aber verkaufen wenn er nur einmal diese 30 Sekunden warten muss bis alles generiert ist aber er dann relativ adhoc in den Daten rumfiltern kann.

Deswegen war auch meine Idee das der Server bzw. die API alles handelt. Also das Ergebnis der Stored Procedure im Cache speichert, die Filterung ausführt und dann die Top 100 Zeilen zurückgibt. Bei einer erneuten Filterung muss dann nur noch auf den Cache zugegriffen werden.

04.11.2020 - 15:42 Uhr

Hallo zusammen,

wir haben den folgenden Use Case zu entwickeln:

Eine Webanwendung, die eine Datentabelle mit bis zu 100.000 Zeilen und 30-50 Spalten anzeigt. Jede Spalte ist filterbar und sortierbar. Die Webanwendung basiert auf Angular und PrimeNG (mit PrimeNG Table) und das Backend basiert auf ASP.Net Core WebApi, aber ich denke, das spielt keine Rolle.

Die Benutzer sind sehr pingelig, daher ist die Performance eine Top-Priorität, und die Anwendung sollte skalierbar sein. Das Filtern einer Tabelle mit 100.000 Zeilen sollte idealerweise weniger als 5 Sekunden dauern.

Es gibt zwei Besonderheiten in unserer Umgebung:

  • Der Datensatz für die Webanwendung wird in einer SQL-Stored procedure generiert. Die komplexe Logik zur Aufbereitung der Daten befindet sich also nicht in der API selbst.

  • Die Webanwendung wird Echtzeitkommunikation über SignalR implementieren. Wenn also ein Benutzer Daten ändert, erhalten alle anderen Benutzer diese Änderungen sofort.

Was wir vorläufig ausgeschlossen haben..

Im Moment denke ich, dass es keine gute Idee ist, den gesamten Datensatz von der API herunterzuladen und den Client das ganze Filtern/Sortieren übernehmen zu lassen.

Alles würde im Javascript - Code passieren. Und ich kann mir nicht vorstellen das dies bei großen Datenmengen noch schnell ist. Zudem hängt es auch vom PC des Endnutzers ab.

Deshalb dachten wir an das gute alte Paging.

Unsere Idee

Ich habe diesen Prozess vor Augen:

Web Client sendet Anfrage an API -> API ruft SQL-Stored Procedure auf und speichert das Ergebnis in einem Cache Store wie Redis oder ähnlichem und gibt dann die erste Page (die ersten 100 Zeilen) an den Client zurück.

Jetzt, wenn der Benutzer die Daten filtert / sortiert oder zu einer anderen Page wechselt:

Web Client sendet Anfrage an API mit der "CacheId" (oder etwas, um den Datensatz im Cache zu identifizieren) -> Wenn die API eine CacheId erhält, versucht sie zuerst, die Daten aus dem Cache zu laden, anstatt die SQL Stored Procedure erneut aufzurufen. -> Die API wendet Filter, Sortierung, Paging usw. an und gibt dann das Ergebnis an den Client zurück.

Auf diese Weise wird die SQL-Stored Procedure (der teure Part) nicht jedes Mal aufgerufen und auch nur ein kleiner Teil der Daten wird an den Client übertragen.

Natürlich entstehen bei dieser serverseitigen Lösung aber auch mehr Anfragen an die API, welche natürlich auch einen gewissen Zeit-Overhead besitzen.

Unser Hauptproblem / Sorgen

Wie soll die Echtzeit-Kommunikation mit dieser Architektur gehandhabt werden? Wenn jemand etwas an den Daten ändert, müssen alle zwischengespeicherten Datensätze aktualisiert werden. Es ist keine Option, die Datensätze im Cache ungültig zu machen und von Grund auf neu zu generieren, wenn jemand irgendwas ändert. Da Datenänderungen sehr häufig (jede Minute) auftreten wäre es sehr rechenintensiv ständig den kompletten Cache neu zu generieren.

Wir brauchen also so etwas wie das hier:

Benutzer ändert Daten und sendet "DataChanged-Event" -> Datensätze werden aus dem Cache gelesen -> Datensätze werden aktualisiert und dann in den Cache zurückgeschrieben

Unsere Fragen

  • Sind wir mit unserer Lösung auf dem richtigen Weg, oder machen wir etwas völlig falsch? Sollten wir doch alles Client-seitig machen? Client-seitig hätte natürlich den Vorteil von generell weniger API-Anfragen und weniger Netzwerklatenz, weil nur einmal ein Zugriff erfolgt um die Daten zu laden. Andererseits hat man dann wieder die Client-seitigen Performance Limitierungen.

  • Wo sollen wir den Cache-Update-Prozess ansiedeln? Vielleicht eine Azure-Function / Konsolenanwendung oder ähnliches, das alle "DataChanges Events" empfängt und dann den Cache aktualisiert?

Wir würden uns über ein paar Anregungen freuen 😃

11.09.2018 - 13:46 Uhr

Hallo zusammen,

wir haben in unserem Unternehmen bisher weitgehend monolithische Anwendungen programmiert. Vereinzelt auch REST -Services und Client -Server Architekturen aber es war nicht die Regel. Das Unternehmen hat sich jetzt einer Cloud - Migration verschrieben in dessen Zuge wir auch unsere Anwendungen modernisieren. Alle neue Anwendungen sollen als Webanwendungen umgesetzt werden und die alten nach und nach neu entwickelt werden.

Jetzt stehen die ersten zwei Projekte dazu an (ein kleineres und ein mittleres). Ziemlich abgetrennte / isolierte Anwendungen und daher ideal zum "üben".

Da wir aus der .Net Welt kommen haben wie uns natürlich für ASP.Net Core und Angular (wegen TypeScript) entschieden. Dafür bilden wir uns hier intern mit Büchern, Videos etc. fort.

Was wir jetzt noch suchen wäre etwas aus dem Bereich Softwarearchitektur von Modernen Webanwendungen. Also die Grundprinzipien wie man sowas aufbaut. Wir würden dazu gerne eine Schulung besuchen. Diese Schulung sollte einen guten Überblick verschaffen was die Prinzipien sind und was es für Möglichkeiten gibt. Wie man jetzt konkret sowas wie Microservices umsetzt wäre, denken wir, zuviel im ersten Schritt.

Was haltet ihr von dieser Schulung? Habt ihr andere Empfehlungen für Anbieter?

https://ppedv.de/schulung/kurse/Softwarearchitektur-HMTL5-CSS-JavaScript-ASPNETMVC-Pattern-MVVM-VisualStudio-Seminar-Training.aspx

Danke.

15.11.2017 - 15:53 Uhr

@UndercoverDeveloper
Wenn du deine Schichten in DLLs packst, kannst du diese bei richtiger Referenzierung nicht mischen.

Ja so sollte es sein mit den DLLs. Dann hast du recht das unsere noch etwas falsch aufgeteilt sind.

Bezüglich des Projektmanagements ist das natürlich richtig aber wir treffen hier auf eine 100%ige Blockade - Haltung der Geschäftsleitung. Diese möchten wissen wann ein Projekt welches im Q3 (als Beispiel) überhaupt mal in Angriff genommen wird fertig ist. Also in Q3 würden wir überhaupt erstmal einen Gedanken daran verschwenden. Trotzdem will man eine Schätzung in Manntagen haben und anhand dieser Schätzung werden innerlich schon die nächsten Projekte geplant (weil der Entwickler hat dann ja wieder Zeit). Natürlich sagen wir das sowas nicht geht aber das findet keine Ohren. Teufelskreis...

Ich glaube wir schweifen ab. Ich denke die Lösung für das Eingangs genannte Problem hat sich herauskristallisiert. Hartes Refactoring mit Regeln damit es nicht wieder passiert oder dirty optimieren wo es geht um den "Kunden" erstmal zufrieden zu stellen.

Kleiner Tipp, was da auch helfen kann, sowas in Zukunft zu vermeiden: Code-Reviews

Danke für den Input. Das stimmt vollkommen. Code Reviews haben nicht nur den Vorteil Fehler zu finden sondern dadurch findet auch Wissenstransfer statt und man tauscht sich mehr aus. Das sorgt vor allem langfristig für noch mehr Vorteile.

15.11.2017 - 15:17 Uhr

Klingt für mich sehr nach mangelhaften Kentnissen im Bereich Architektur bei euch "normalen" C# Entwicklern.

Ja, alles richtig was du sagst. Bis auf die Unterteilung der Schichten in mehrere DLLs. Auch das haben wir gemacht, aber das ändert nichts daran das alle 3 Schichten immer noch vermischt werden können. Vollkommen richtig das es hier ein besseres Konzept nötig ist,dass dem Entwickler die Trennung der Schichten deutlicher macht und eine Vermischung nicht mehr so leicht oder sogar "aus Versehen" geschieht.

Off-Topic:

Der Rest ist auch richtig aber das geht jetzt weit in Richtung Firmenpolitik und wieso und weshalb es dazu gekommen ist. Bei uns ist die Abteilung so strukturiert: Entwickler -> Geschäftsleitung (Kein IT - Leiter!). Jeder Entwickler arbeitet an seinen eigenen Projekten. Kontrollinstanzen gibt es nicht. Anforderung geht direkt an Entwickler -> Von Konzeption bis Roll-Out ist das dann größtenteils eine One-Man-Show.

Zielvereinbarungen sehen wie folgt aus: Ich will das in Q2 2018 haben! -> Schau zu das es dann fertig ist. Zu diesem Zeitpunkt wurden noch keine Anforderungen erhoben sodass eine Schätzung möglich ist.

Genauso mit dem aktuellen Projekt -> Das Datum steht wann es fertig sein soll aber ich weiß noch gar nicht was alles benötigt wird. Deswegen muss ich hier jetzt nach einer "intelligenten" (sicherlich die falsche Wortwahl) Lösung suchen. Dadurch entstehen dann eben auf Dauer die Dirty - Lösungen wie diese Software weil für mehr keine Zeit ist.

Wir sehen die Probleme, sind motiviert es richtig zu machen und wollen dazu lernen. Das ist ein strukturelles Problem.

15.11.2017 - 11:50 Uhr

Danke für die Antworten. Ich dachte mir schon fast das es nicht einfach ist, es keine allgemeine Lösung / Vorgehensweise gibt und es einfach zu stark von der jeweiligen Software abhängt.

Eine hartes Refactoring könnte durchaus die einzig richtige Lösung sein. Dazu habe ich mich zwar noch nicht überwunden und versuche noch andere Wege zu evaluieren. Im Moment sind das aber alles eher Zwischenlösungen die zwar den Kunden (Geschäftsleitung) erstmal zufrieden stellen aber das Problem nur verschleppen und es kracht sowieso irgendwann.

Zu 1): seid ihr euch sicher, dass die OutOfMemory Exception wirklich vom Speicher kommt? Entgegen des Namens gibt es auch andere Auslöser wie zB. zu viel offene Handles.

Ja da sind wir sicher. Fehler wegen zu vieler Handles kriegen wir übrigens auch aber das ist ein anderes Thema.

Habt Ihr das Projekt mal mit einem Memoryprofiler unter die Lupe genommen um zu sehen welche Objekte wirklich viel Speicher verbrauchen?

In 2 Jahren sieht die Anwendung wieder so aus.

Gibt's keine Richtlinien und Vorgesetzte die das Einhalten der Richtlinien durchsetzen können?

Das mit dem Memoryprofiler habe ich gemacht. Das sind größtenteils einfach die Datenbank - Rows die in das Programm geladen werden. Teilweise werden plump ganze Tabellen heruntergeladen. Dann hat man gleich nach nach Programmstart mehrere hunderttausende Objekte im Arbeitsspeicher die dann eben auch mal 500MB Platz benötigen. Sicherlich gibt es Tabellen die mehr und welche die weniger Platz benötigen. Wenn ich anfange aufzuräumen würde ich das entsprechend priorisieren.

Schlimmer wirds noch wenn während der Laufzeit Bilder aus der Datenbank runtergeladen werden (eine wichtige Aufgabe der Software ist das Arbeiten mit Bildern). Durch die hohe Grundauslastung an Arbeitsspeicher und fehlendes richtiges Caching (Speicher sollte auch wieder freigegeben werden) kommen wir irgendwann in einen kritischen Bereich.

Richtlinien in diesem Sinne gibt es nicht. Es gibt allgemeine Coding - Guildlines die sich aber mehr auf Naming und Formatierung beziehen. Es gibt aber keine Richtlinien bezogen auf die Software -
Architektur die dazu zwingen das sowas nicht passiert. Geschweigen von Code Reviews um die Einhaltung der Richtlinien zu prüfen.

Das Hauptproblem eurer Anwendung scheint ja zu sein, daß ihr keine vernünftige, einheitliche Architektur habt, wie in
>
beschrieben.

Du solltest also ersteinmal dafür sorgen, daß der DataAccess nur noch von den BusinessLogic aus aufgerufen wird (und wenn es ersteinmal nur Wrapper-Methoden sind).
Gerade wenn euer Projekt noch Jahre weiterlaufen soll (und nicht einfach nur noch ein Legacy-Projekt ist). Habt ihr denn keinen Software-Architekten für das Projekt?

So wie Abt schreibt, sehe ich es auch. Je nachdem wie der Rest der Software geschrieben ist, ist es überhaupt ausreichend nur ein Soft-Refaktorierung hier zu machen?

Nein, wir haben keinen Software-Architekten. Wir sind alles "normale" C# Entwickler die sich eben um alles kümmern. Das ist halt in vielen Backoffice Abteilungen so...

Eine gewisse Art von Architektur gibt es schon. Also wir öffnen im CodeBehind einer Form keine Datenbank - Connection und sowas. Es gibt die Forms, darunter gibt es die BusinessLogic.cs (ja sehr riesige Datei) und darunter die Klassen DataStore (Abbild der Tabellen) und DataAccess (Zugriff auf die Tabellen). Nur ganz grob erklärt...

Trotzdem befindet sich im CodeBehind viel Logik - Code der da nicht hingehört und unsere 3 -Schichten Architektur (vom Grundgedanken ist es ja eine) lässt sich sehr leicht aufweichen und ist sehr aufgeweicht. Teilweise werden die DataAccess - Funktionen (z.B AppointmentGetById) durch die BusinessLogic.cs einfach 1:1 durchgeschleift und dann im CodeBehind aufgerufen. Sicherlich sollte genau das nicht passieren und nur die BusinessLogic.cs Zugriff auf die direkte Datenbankkommunikation haben. Konzepte wie MVC, MVP, MVVM wären bestimmt besser aber das käme einer Neuentwicklung gleich.

Ich denke weiter darüber nach.... . Wenn ihr Input habt dann immer her damit.

Danke & LG

14.11.2017 - 11:33 Uhr

Hallo,

ich habe ein Problem was ich gerne mal in die Runde werfen würde.

Ausgangssituation:

Es gibt eine große Software die über Jahre gewachsen ist. Aufgrund fehlender Zeit für Wartungsarbeiten / Optimierungen sind diverse Probleme entstanden. Jetzt soll die Anwendung aber modernisiert werden. Hauptproblem ist aktuell die Datenschicht der Anwendung. Die Anwendung setzt auf einen selbst entwickelten OR - Mapper.

  1. Je nachdem wie viele Berechtigungen man in der Anwendung hat, kann es gut sein das die Anwendung bereits nach Programmstart 500-600MB Arbeitsspeicher verbraucht. Vereinzelt bekommen wir schon OutOfMemory Exceptions da die Datenmenge während der Programmlaufzeit weiter anwächst.

Bei Programmstart werden also eine Menge Daten aus der Datenbank geholt die eventuell zu diesem Zeitpunkt nicht benötigt werden und dann auch nicht mehr gelöscht werden.

  1. Jeder Programmierer der mal an dieser Anwendung gearbeitet hat, musste selber zusehen das er die Daten aus der Datenbank bekommt die er gerade benötigt. Zu Sicherheit wird dann lieber nochmal eine extra Datenbankabfrage gemacht. Das führt jetzt dazu das z.B 5 mal die Datenbank angefragt wird obwohl die Daten vielleicht schon im Arbeitsspeicher liegen.

  2. Die Datenbankabfragen sind über die gesamte Anwendung verstreut. Es gibt zwar eine Klasse, die nennt sich DataAccess. In dieser sind die SQL Statements gebündelt und werden durch Funktionen wie "LocationListGetByOwningPerson", "LocationListGetByNumber", "LocationListGetByblablabla..." nach außen gegeben. Diese Funktionen ruft jeder Entwickler aber an jeder Stelle auf wo er gerade lust dazu hat. In der BusinessLogic, im CodeBehind von Winforms usw.

Lösungansätze:

Für ein besseres Caching lohnt sich sicherlich ein Blick auf System.Runtime.Caching (MemoryCache). Somit kann man Daten die z.B länger als 10 Minuten nicht verwendet wurden aus dem Speicher löschen und verhindert somit ein weiteres ansteigen der Speicherauslastung.

Dann müsste ich im zweiten Schritt verhindern das bei Programmstart nicht so viele Daten ausgelesen werden und die ganzen unnötigen Aufrufe aus der Klasse "DataAccess" irgendwie konsolidieren. Aber ich glaube genau das ist ein Fass ohne Boden. Viele Funktionen verlassen sich darauf das bei Programmstart viele Daten ausgelesen werden. Zu evaluieren welche wirklich benötigt werden ist schwierig. Man ist damals nach dem Motto vorgegangen "Lieber mehr als zu wenig."

Daten die dann bei Programmstart nicht mehr ausgelesen werden, müssen an geeigneter Stelle wieder geholt werden. Außerdem...selbst wann ich mir einmal die Mühe mache und die Datenbankzugriffe konsolidiere und nicht mehr so viele Daten bei Programmstart einlese und erst bei Bedarf hole, ist das alles nicht Nachhaltig. In 2 Jahren sieht die Anwendung wieder so aus.

Ich hätte gerne eine Lösung die an der Basis ansetzt und nachfolgende Programmierer dazu zwingt es richtig zu machen oder im Idealfall das soweit abstrahiert das sie sich nicht darum kümmern müssen.

Frage:

Sicherlich schwierig zu beantworten ohne das ihr den Code und die Awendung genauer kennt. Das würde aber jetzt den Rahmen sprengen. Gibt es irgendwelche allgemeine Best Practices oder Vorgehensmodelle wie man so alte Anwendungen modernisiert? Besonders im Bezug auf die Datenschicht?

Ich bin für jeden Input dankbar.

Danke & LG

04.07.2017 - 11:22 Uhr

Wir verwenden eine Dokumenten-Basierte bzw. noSQL-Datenbank.

Ich frage mich, wie das dem OP helfen soll. Die App (inkl. Backend) existiert ja schon und so weit ich das verstanden habe steht ein Wechsel der Datenbank nicht zur Diskussion ?(

Richtig. Wir haben eine bestehende SQL - Datenbank die wiederverwendet werden muss. Da jetzt noch irgendeine Middleware zu schreiben die zwischen einer App spezifischen NoSql Datenbank und der eigentlichen SQL-Datenbank übersetzt wäre zuviel des guten. Trotzdem ist das natürlich ein guter Hinweis für die Zukunft falls wir mal eine App auf einer komplett neuen Datenbank entwickeln.

@Sir Rufo:
Ich werde deinen ersten Vorschlag ausprobieren 😃 Das hatten wir gestern im Team schon einmal so ähnlich diskutiert aber zu schnell verworfen weil es zu "dirty" klang. Aber es könnte wirklich eine gute Lösung sein. Man hat natürlich immer etwas Performance Overhead für das erstellen einer neuen SqlLite Datenbank.

Der zweite ist glaube ist nicht so leicht umsetzbar. Das Azure SqlLite SDK (Microsoft.WindowsAzure.MobileServices.SQLiteStore.MobileServiceSQLiteStore) hat nicht so tiefgehende Funktionalitäten um eine Transaktion zu starten. Hier bräuchte ich wieder ein extra SQLLite Framework. Und ich weiß nicht ob sich das dann mit dem Azure SqlLite SDK streitet.

Danke 😃

04.07.2017 - 10:15 Uhr

Hallo zusammen,

ich habe mal eine Frage in die Runde. Wir arbeiten zurzeit in einem kleinen Team an einer iOs / Android App auf Basis von Xamarin.Froms. Einsatzzweck dieser App ist im Bereich des Vertriebs-Außendiensts. Das heißt, ein Außendienstmitarbeiter besucht Märkte / Standorte, macht dort verschiedene Dinge und reportet das ganze über die App. Dabei ist es extrem wichtig das die App immer funktioniert, also auch wenn keine Internetverbindung besteht.

Als Backend haben wir uns daher für Microsoft Azure App Services entschieden. Azure bietet eben genau diese Offline-Synchronisation. Alle Daten liegen lokal und werden bei bestehender Internetverbindung mit dem Server synchronisiert. Die Datenhaltung auf dem Server geschieht über einen SQL Server 2016 und auf dem Endgerät wird SQLLite verwendet. Der Datenabgleich muss explizit vom Anwender gestartet werden, hierfür gibt es einen Button in der App.

Die Problemstellung:

Jede Tabelle wird einzeln beim Server angefragt und heruntergeladen. In der Regel kann eine Tabelle auch nicht komplett auf einmal heruntergeladen werden weil es zu viele Daten sind. Pro Tabelle können auch mehrere Zugriffe auf den Server notwendig sein.

Azure App Services bietet keine Möglichkeit diese Anfragen in einer Transaktion zu bündeln und gegebenenfalls einen Rollback auszuführen. Bricht der Datenabgleich (wieso auch immer) mittendrin ab kann es passieren das der Datenbestand auf dem Endgerät inkonsistent ist und die App daher nicht verwendet werden kann. Die Datenbankstruktur ist recht komplex und der Normalisierungs-Grad der Datenbank recht hoch, was bedeutet das es viele Abhängigkeiten zwischen den Tabellen gibt und nicht einfach alle zusammengehörige Daten in einer Tabelle stehen. Wenn z.B Tabelle A erfolgreich heruntergeladen wird aber Tabelle B nicht, können Daten aus Tabelle A nicht verwendet werden weil eben unvollständig.

Unsere Lösungsansätze:

  1. Läuft die Datensynchronisation nicht erfolgreich bis zum Ende durch wird die App gesperrt und der Anwender wird dazu aufgefordert die Datensynchronisation zu wiederholen bis sie erfolgreich war.

Bewertung: Das ist eigentlich ein Nogo. Nehmen wir an, der Außendienstmitarbeiter steht vor dem Markt und raucht gerade noch eine und denkt sich das er in dieser Zeit doch kurz mal einen Datenabgleich ausführen könnte. Dieser bricht aber ständig aufgrund der schlechten Internetverbindung ab. Jetzt steht der Außendienstmitarbeiter im Markt und kann die App nicht verwenden weil diese gesperrt ist.

  1. Wir bauen eine Logik in die App ein welche prüft ob ein Datensatz verwendet werden kann. Soll z.B auf einen Datensatz in Tabelle A zugegriffen werden muss zu nächst geprüft werden ob die passenden Relations in Tabelle B, C, D, E, F und G vorhanden sind. Wahrscheinlich müssen hierzu auch "Checksums" in der Serverdatenbank hinterlegt werden. Es gibt z.B die Möglichkeit in der App einen Fragebogen auszufüllen. Das macht aber nur Sinn wenn dieser auch vollständig heruntergeladen wurde. Also muss auch irgendwo hinterlegt sein was der Fragebogen alles beinhaltet. Außerdem muss natürlich bei JEDEM Objektzugriff überprüft werden ob irgendetwas Null ist damit es zu keinem Fehler kommt.

Bewertung: Ich denke es ist ersichtlich das sowas recht kompliziert ist und daher fehleranfällig. Für jeden Datentyp müssen individuelle Prüfroutinen geschrieben werden da sich diese nicht verallgemeinern lassen. Außerdem ist es sehr wahrscheinlich das hier schnell mal was übersehen wird und dann kommt es am Ende doch wieder zu einem Fehler in der App. Um hier keinen Fehler zu machen ist es sehr wichtig den Code gut zu kennen und vor allem die Datenbankstruktur, was die Einarbeitung neuer Programmierer in dieses Projekt zu einer sehr großen Herausforderung werden lässt.

Jetzt würde ich gerne euch fragen ob ihr eine Idee habt wie man so etwas handeln kann. Ich denke das die Problemstellung von der verwendeten Technik losgelöst ist und es sich mehr um eine generelle Softwaredesign - Frage handelt. Wie würdet ihr an dieses Problem heran gehen?

Wir freuen uns über jeden Input zu diesem Thema. Vielen Dank 😃

26.03.2015 - 17:54 Uhr

Hallo zusammen,

wir haben ein Problem im WCF-Umfeld mit einer .NET-Anwendung (Windows Forms), die wir programmieren und betreuen. Der Sachverhalt ist etwas komplizierter, aber ich hoffe es verständlich erklären zu können.

Die Anwendung ist Multiuser-fähig und es arbeiten gleichzeitig etwa 5-10 Benutzer damit. Alle Benutzer starten die Anwendung über die gleiche EXE-Datei, die auf einem für alle erreichbaren Netzlaufwerk liegt. Verbunden sind die einzelnen gestarteten Anwendungen (Clients) mit einem auf WCF basierenden WebService, in den sich jeder gestartete Client über eine „SignIn“-Methode „einloggt“ und der anschließend jeden einzelnen Client informiert sich selbst zu aktualisieren, wenn die Information über eine Datensatz-Änderung den WebService erreicht.

Der Webservice selbst führt keine größeren Aktionen durch, sondern ist nur dafür zuständig „Aktualisierungs-Pings“ an die eingeloggten Clients zu senden (über CallBacks) und eine Liste der Benutzer zu verwalten, die jeweils einen Client gestartet haben.

Dies hat mehrere Monate nahezu reibungslos funktioniert. Seit einiger Zeit kommt es beim Aufruf von Funktionen des WebServices durch bestimmte gestartete Instanzen der Anwendung zu folgendem Fehler:

Fehlermeldung:
Dieser an
>
gesendete Anforderungsvorgang hat innerhalb des konfigurierten Zeitlimits (00:00:59.9843743) keine Antwort empfangen. Der diesem Vorgang zugewiesene Zeitraum ist möglicherweise ein Teil eines längeren Zeitlimits. Mögliche Ursachen: Der Dienst verarbeitet den Vorgang noch, oder der Dienst konnte keine Antwortnachricht senden. Erwägen Sie, das Zeitlimit für den Vorgang zu erhöhen (indem Sie den Kanal/Proxy in IContextChannel umwandeln und die OperationTimeout-Eigenschaft festlegen), und stellen Sie sicher, dass der Dienst eine Verbindung mit dem Client herstellen kann.

StackTrace:

Fehlermeldung:
Server stack trace:
bei System.ServiceModel.Dispatcher.DuplexChannelBinder.SyncDuplexRequest.WaitForReply(TimeSpan timeout)
bei System.ServiceModel.Dispatcher.DuplexChannelBinder.Request(Message message, TimeSpan timeout)
bei System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
bei System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs)
bei System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
bei System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

Exception rethrown at [0]:
bei MyCompany.MyProgram.Windows.BusinessLogic.SignIn(Boolean forced) in C:\Projekte\MyCompany\MyProgram\Windows\BusinessLogic\1.0\BusinessLogic.cs:Zeile 193.

Der Ablauf an der Stelle, an der der Fehler auftritt, ist folgender:
Client ruft die SignIn() Methode des WebServices auf -> WebService aktualisiert die Login-Liste und sendet ein CallBack an alle Clients -> Client holt sich die aktualisierte Loginliste.

Der Fehler tritt allerdings nicht bei jedem Anwender auf sondern nur in einer Wechselwirkung mit einem anderen Anwender, wenn dieser auch den Client geöffnet hat. Anscheinend sind in bisher jedem Fall zwei Benutzer betroffen, die direkt in einer Abteilung zusammenarbeiten. Jeder hat dabei seine eigene Workstation.

Hat also Kollege A eine Instanz der Anwendung geöffnet bekommt Kollege B in der gleichen Abteilung den Fehler, aber jeder andere aus anderen Abteilungen kann ohne Probleme mit dem WebService kommunizieren. Schließt Kollege A seine Instanz der Anwendung kann Kollege B dann wieder mit dem WebService kommunizieren. Die Useraccounts sind innerhalb der Anwendung aber nicht einzelnen Abteilungen zugeordnet und die Abteilung hat daher auch keine Auswirkung auf das Verhalten des Codes.

Wir sind bei der Fehlersuche bereits auf das Problem gestoßen das CallBacks deadlocks verursachen können (https://msdn.microsoft.com/en-us/magazine/cc163537.aspx#S4).

Allerdings helfen die möglichen Lösungen nicht und erklären auch nicht das ganze Problem (insbesondere nicht die Wechselwirkung bei den Anwendern)
Wegen der Wechselwirkung haben wir auf ein Netzwerkproblem geschlossen, da sich am Code auch nichts geändert hat und das Problem erst seit kurzem auftritt. Dazu haben wir aber auch keine Idee wie wir das eingrenzen sollen.

Ich spare es mir erstmal Code zu posten da der Fehler nicht so offensichtlich ist und unserer Meinung nach nicht direkt im Code zu sehen ist. Falls ich euch konkret mit weiteren Informationen versorgen kann dann sagt einfach Bescheid.

Habt ihr eine Idee? Wir nehmen gerne jeden Einfall entgegen auch wenn es keine direkte Fehlerlösung ist.

Viele Grüße