Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Delphi OutOfMemory Fehler abfangen (https://www.delphipraxis.net/189851-outofmemory-fehler-abfangen.html)

Aviator 29. Jul 2016 23:57

OutOfMemory Fehler abfangen
 
Hallo zusammen,

aktuell stehe ich vor dem Problem, dass ich sehr sehr sehr viele Datensätze aus einer SQLite Datenbank laden muss. An dieser Stelle kann ich auch nichts ändern, da der User explizit so viele Datensätze anfordert. Kann also in dem Fall nicht virtuell arbeiten. In gewissem Maße wird das sogar schon gemacht, da mit dem VirtualTreeView gearbeitet wird. Die Daten werden im Hintergrund in einer komplexen Datenstruktur vorgehalten.

Das Problem ist jetzt, dass bei so vielen Datensätzen der nutzbare Arbeitsspeicher für das Programm ausgeht (32-bit Anwendung). Bei 64-bit kein Problem. Jetzt stelle ich mir nur die Frage, wie ich einen solchen Fehler abfangen kann. Ich merke ja erst dann wenn die Exception ausgelöst wird, dass der Arbeitsspeicher voll gelaufen ist. Nachdem die Exception ausgelöst wurde, kann ich aber meine Struktur im Hintergrund nicht mehr freigeben um nochmal etwas Arbeitsspeicher frei zu machen. Die Folge wenn ich das mache ist, dass die nächste Exception mit OutOfMemory ausgelöst wird.

Wie macht ihr sowas oder kann man das überhaupt abfangen? Falls ihr noch Informationen braucht, dann kann ich euch die gerne geben. Es sei denn es wäre SourceCode. Da müsste ich schauen, was ich veröffentlichen kann. Aktuell geht es mir aber erstmal um das generelle OutOfMemory Problem.

EWeiss 30. Jul 2016 02:11

AW: OutOfMemory Fehler abfangen
 
Zitat:

kann ich aber meine Struktur im Hintergrund nicht mehr freigeben
Kann man immer an der richtigen stelle.
Wenn die aktuelle Datenbank nicht mehr verwendet wird (Aber ich denke das tust du bereits)

Das Problem ist nur wenn die Datenbank so groß ist das sie den verfügbaren Speicher sprengt und du zu der zeit nur mit einer arbeitest
was will man dann freigeben wenn sie gerade in gebrauch ist.

Was ich mir vorstellen könnte die Datenbank zu splitten und den gerade nicht verwendeten Teil davon freizugeben.
Also eine Datenbank in kleinere Häppchen aufteilen.

gruss

sh17 30. Jul 2016 05:51

AW: OutOfMemory Fehler abfangen
 
Würde mich auch interessieren, hab das gleiche Problem nämlich beim Drucken unter Nutzung von GDI+. Würde sich das bei Win64 dann erledigen? Konnte ich noch nicht prüfen.

HolgerX 30. Jul 2016 09:36

AW: OutOfMemory Fehler abfangen
 
Hmm..

Bei so vielen Daten solltest Du Dir vielleicht auch Gedanken über ein anderes DB-System machen...

Z.B. gibt/gab es bei ADO die Möglichkeit mit CursorLocation=clUseServer die Daten Sequentiell ab zu rufen..

In Verbindung mit einem SQL-Server hat dies dazu geführt, das die Datenmenge immer nur einen Teil geladen hat und erst beim Scrollen z.B. im DBGrid den nächsten Teil nachgeladen hat.

Dies kannst Du natürlich auch selber Programmieren...

Aviator 30. Jul 2016 10:10

AW: OutOfMemory Fehler abfangen
 
Zitat:

Zitat von sh17 (Beitrag 1343774)
Würde mich auch interessieren, hab das gleiche Problem nämlich beim Drucken unter Nutzung von GDI+. Würde sich das bei Win64 dann erledigen? Konnte ich noch nicht prüfen.

Also bei 64-bit hast du ja viel mehr Arbeitsspeicher zur Verfügung. Von daher sind dort dann solche Probleme weg. Ich weiß nur nie genau, ob man beim Umstieg von 32-bit auf 64-bit an der Art der Programmierung (Datentypen usw.) etwas ändern muss bzw. sollte.

Zitat:

Zitat von HolgerX (Beitrag 1343781)
Bei so vielen Daten solltest Du Dir vielleicht auch Gedanken über ein anderes DB-System machen...

Das ist leider nicht möglich. Ich schreibe aktuell ein Programm, dass eine SQLite Datenbank auslesen kann. Also eine Art Management Studio für SQLite. Bevor du (oder jmd. anderes) allerdings sagt, dass es solche Programme schon zu genüge gibt: Ich weiß das. Aber ich würde trotzdem gerne ein eigenes schreiben. :stupid:

Mein Programm funktioniert ja auch schon, aber es gibt bei so viele Datensätzen eben Speicherprobleme. Allerdings nur in Verbindung mit Strings. Wenn nur Zahlen o.ä. in der Abfrage enthalten sind, dann sind 10Mio Datensätze auch kein Problem.

Ein virtuelles Auslesen der Datenbank ... keine Ahnung wie das gehen soll. Zumindest nicht, ohne jetzt mein komplettes Konzept über den Haufen zu werfen. Das war schon sehr kompliziert, überhaupt eine Struktur zu entwickeln um alle Daten darin zu speichern. Wer genaueres Wissen will, sollte sich das Thema SQLite Typaffinitäten auf der SQLite HomePage anschauen.

Zitat:

Zitat von EWeiss (Beitrag 1343773)
Zitat:

kann ich aber meine Struktur im Hintergrund nicht mehr freigeben
Kann man immer an der richtigen stelle.
Wenn die aktuelle Datenbank nicht mehr verwendet wird (Aber ich denke das tust du bereits)

Tja die Datenbank selbst ist ja nicht das Problem. Das ist ja nur ein einziger Pointer der von einer DLL zurückgegeben wird. Problematisch sind die Queries die ausgeführt werden. Aber auch nur die, die der User ausführen kann, weil dort zu viele Datensätze zurückkommen könnten.

Wenn ich jetzt ein
SQL-Code:
SELECT * FROM Daten
mache und ein
SQL-Code:
LIMIT 1000
vergesse, dann können da je nach Tabelle viele viele Datensätze zurückkommen. Und das weiß ja auch mein Programm vorher nicht. Es sei denn ich würde einen entsprechenen Count vorher ausführen. Aber dann weiß ich immer noch nicht, wie viel RAM die Daten belegen würden.


Es muss doch eine Möglichkeit geben, aus dem Teufelskreis wieder herauszukommen. Ich kann die Exception ja abfangen, aber jegliche weitere Aktion die ich dann ausführen will, führt zu einer weiteren EOutOfMemory Exception. :wall: Könnte ich an der Stelle, ander die Exception ausgelöst wird meine Daten die ich aus der Datenbank ausgelesen habe wieder freigeben, dann wäre das kein Problem. Aber zum Freigeben wird anscheinend auch etwas Speicher benötigt, was dazu führt das es nicht mehr funktioniert. Ich kann nicht mal mehr den User mit einer etwas freundlicheren Meldung informieren. Dafür ist auch kein Speicher mehr frei. Und das Programm selbst muss danach auch über den Taskmanager beendet werden, da es sich mit der Meldung OutOfMemory nicht mehr beenden lässt.

Bernhard Geyer 30. Jul 2016 10:49

AW: OutOfMemory Fehler abfangen
 
Zitat:

Zitat von HolgerX (Beitrag 1343781)
In Verbindung mit einem SQL-Server hat dies dazu geführt, das die Datenmenge immer nur einen Teil geladen hat und erst beim Scrollen z.B. im DBGrid den nächsten Teil nachgeladen hat.

Du weißt aber schon das damit der Server sehr stark belastet wird. Die 2 GB die du nicht mehr auf Clientseite hast muss der Server verwalten.
Und schneller wird die Lösung nicht wenn man meint Mio. Datensätze in einem Grid zu haben.

Wir haben eine Lösung implementiert zuerst nur die Primärschlüsselspalten zu laden und erst bei der Anzeige die weiteren Spalte nachzuladen.

Sir Rufo 30. Jul 2016 11:12

AW: OutOfMemory Fehler abfangen
 
Der Anwender tippt ein
SQL-Code:
select * 
from hugetable
dein Programm führt aus
SQL-Code:
select * from (
  select * 
  from hugetable
) limit 1000 offset 0
Schon gibt es immer nur max. 1000 Zeilen, egal was der Benutzer dort eintippt

Aviator 30. Jul 2016 15:35

AW: OutOfMemory Fehler abfangen
 
Zitat:

Zitat von Sir Rufo (Beitrag 1343786)
Der Anwender tippt ein
SQL-Code:
select * 
from hugetable
dein Programm führt aus
SQL-Code:
select * from (
  select * 
  from hugetable
) limit 1000 offset 0
Schon gibt es immer nur max. 1000 Zeilen, egal was der Benutzer dort eintippt

Ja die Idee hatte ich auch schon, aber wie setze ich das am Besten um? Ich muss ja dann immer die Daten aus dem zurückgegebenen ResultSet auslesen und an die bereits ausgelesenen Datensätze anhängen wenn der User im Ergebnisbaum scrollt. Es würde zwar sehr lange dauern bis der User ganz unten ankommt, aber theoretisch könnte der Fehler dann ja immer noch auftreten. Die bereits ausgelesenen Datensätze löschen und die Liste immer von vorne füllen ... ich weiß nicht. Ich muss ja auch den Ergebnisbaum so aufbauen, dass der wenigstens so aussieht, als wären 100.000 Datensätze geladen. Dann müsste ich mir ja auch noch merken, wie weit ich bereits war. OK das wäre das Offset das du in deinem Statement beschreibst.

Kannst du das vielleicht noch etwas genauer ausführen?

mensch72 30. Jul 2016 15:53

AW: OutOfMemory Fehler abfangen
 
in jeder vernünftigen DB-Struktur wird es ja einen PrimaryKey geben, also was spricht dagegen zunächst mal kein
Code:
select * from hugetable
sondern nur ein
Code:
select indexfield from hugetable
zu machen?



Wenn da auch 1mio als ResultSet zurückkommt wird das noch locker in den RAM passen.

Bei der Anzeige(funktioniert super in Grids&VST) wird dann "alles" LineByLine einzeln bei bedarf per separater Query abgefragt und live angezeigt/ausgewertet. Da ist zunächst stets etwas langsamer in Anzeige und Scroll, dafür aber absolut konstant im Verhalten bei quasi beliebig vielen Daten, was der SQL-Server eben hergibt.

Code:
select * from hugetable where indexfield='IndexValue'

Aviator 30. Jul 2016 16:32

AW: OutOfMemory Fehler abfangen
 
Zitat:

Zitat von mensch72 (Beitrag 1343797)
in jeder vernünftigen DB-Struktur wird es ja einen PrimaryKey geben, also was spricht dagegen zunächst mal kein
Code:
select * from hugetable
sondern nur ein
Code:
select indexfield from hugetable
zu machen?

Halte ich jetzt nicht für die optimale Idee. In diesem Fall müsste z.B. immer eine Verbindung zur Datenbank bestehen. Wenn diese auf einem Netzlaufwerk liegt, kann es auch schonmal vorkommen, dass das Netzwerk nicht funktioniert. In dem Fall wäre ich bei deiner Lösung in den aktuell angezeigten Zeilen gefangen. Mit meiner Lösung könnte ich weiterhin alle Daten durchsuchen, scrollen und bspw. auch kopieren.

Gibt es denn keine Möglichkeit, ein OutOfMemory Fehler abzufangen und die Instanzen wieder freizugeben die den Speicher belegen?

Mavarik 30. Jul 2016 16:57

AW: OutOfMemory Fehler abfangen
 
Ich kann mir momentan keine Situation vorstellen die es nötig macht 1Mio Datensätze oder mehr im Speicher zu halten?

Lade 100 - wahrscheinlich werden nur 40 auf einmal angezeigt... Wenn der User noch unten Scrollt lade nochmal 100 und vergiss die 1. 50... Fertig..

Abgesehen davon macht das FireDac sowieso - es sei den Du hast FetchAll gesetzt...

Oder mache es wie ein eMail Programm - lade 1000 Header und nur bei Bedarf, die entsprechende Daten die dahinter stecken...

Mavarik

Aviator 30. Jul 2016 17:13

AW: OutOfMemory Fehler abfangen
 
Ich werde es wohl so mal probieren müssen. :?

Aber es muss doch eine Möglichkeit geben, so einen Fehler generell abfangen zu können. :shock:

Ich greife übrigens nicht mit FireDac auf die Datenbank zu sondern benutze zum Zugriff auf die Datenbank direkt die sqlite3.dll. Die Daten werden, wie vorher beschrieben, in einer eigenen Datenstruktur aufbewahrt.

mensch72 30. Jul 2016 17:29

AW: OutOfMemory Fehler abfangen
 
Zitat:

Zitat von Aviator (Beitrag 1343799)
... In diesem Fall müsste z.B. immer eine Verbindung zur Datenbank bestehen. Wenn diese auf einem Netzlaufwerk liegt, kann es auch schonmal vorkommen, dass das Netzwerk nicht funktioniert. In dem Fall wäre ich bei deiner Lösung in den aktuell angezeigten Zeilen gefangen. Mit meiner Lösung könnte ich weiterhin alle Daten durchsuchen, scrollen und bspw. auch kopieren.

Gibt es denn keine Möglichkeit, ein OutOfMemory Fehler abzufangen und die Instanzen wieder freizugeben die den Speicher belegen?

Ähhh, wenn alles in RAM laden nicht will, bleibt doch nur der live Zugriff auf die Daten(bank), welche schrittweise(mein Beispiel) oder blockweise(siehe SQL Beispiel mit den 1000) je nach Bedarf live nachlädt.

Wenn die Datenquelle extern/unsicher ist, dann muss man sich eben lokal auf Disk (und nicht im RAM) eine Kopie als Zugriffscache erzeugen (also lokale SQliteDB holt sich zunächst per "BatchMove" gemäß UserSQL das was gewünscht ist und speichert das als "ViewResultTable" lokal. Anzeige und Verarbeitung gehen dann Schrittweise/Bockweise mit einer Query auf diese lokale "ViewResultTable".


Datenbanken im Netzwerk per FileZugriff sollte man nicht (mehr) machen. Dafür nehme man einen echtes DBMS was die Clients direkt per Netzwerkprotokoll anbindet.
Wir nehmen im Netzwerk MS-SQLexpress (nur ganz wenig hat bis jetzt den großen MS-SQL Server wirklich gebraucht und genutzt) und als lokales "SQL" meist sogar noch die "access.mdb" Files (SQlite weil portabel für MAC und Mobile aber jetzt für alles neue mit FMX).
Speziell im MobileBereich machen wir immer einen Kompromiss zwischen offlinefähig gecacheten lokalen (Stamm/View)Daten und 100% aktuellen Gesamt(Live)Daten auf den Servern mit online Zwang.

Aviator 30. Jul 2016 17:40

AW: OutOfMemory Fehler abfangen
 
Ich schreibe ja ein Management Studio für SQLite. In unserer Firma nutzen wir MSSQL. Nur weiß ich ja nicht, wer irgendwann mal auf die Idee kommt und evtl. eine SQLite DB vom Netzwerk aus lädt. Ich würde das auch nicht machen, aber ich bin ja nicht der Endbenutzer. Wenn ich mal fertig bin mit dem Programm, dann dachte ich mir, dass ich es evtl. als Freeware anbiete. Nur bis zu diesem Tag wird es noch ein wenig dauern.

Aber trotzdem nochmal zur eigentlichen Frage zurück: Gibt es keine Möglichkeit, eine OutOfMemory Exception abzufangen und entsprechend nochmal etwas freizugeben? Kann doch nicht sein, dass ein Programm das einmal einen Speicherüberlauf produziert hat nur noch per TaskManager zu beenden ist.

mensch72 30. Jul 2016 18:36

AW: OutOfMemory Fehler abfangen
 
Vergleiche eine OutOfMemory Exeption mal mit "OutOfDiskSpace" , wo du anschließend noch etwas in einer LogDatei abspeichern willst...

Eine OutOfMemory Exeption kannst du eigentlich nur sinnvoll extern mit einem anderem (WatchDog)Prozess behandeln. Unter Win64 hat ja jeder Win32 seinen eigenen "32Bit-Speicherbereich". Wenn in einem Prozess also wegen Speichermangel nix mehr geht, kümmert sich ein anderer externer Prozess dann um das "geordenete" Management der Situation.

Eigene Datenstrukturen, Klassen, Pointer auf Arrayelemente... um eine eigene Logik für "VirtualTable", "VirtualArray" und "CachedCursor" kommt man da dann nicht herum, wenn man eben nicht UniDac oder FireDac nimmt, sondern direkt mit den DB-System ala SQlite3.dll arbeitet.



Ich weiß duraus bei 32Bit-Programmen wo von ich rede und kenne die "physischen Grenzen"...
-> wir "spielen" hier im Standard mit Tabellen, wo aktuell üblich !400Mio! Datensätze(bei uns alle Finanztransaktionsdaten von XY der letzten 10Jahre bis auf die Millisekunde genau) drin sind und da ist ein "select *" mit Scroll als Grid und/oder Chart absolut nix besonderes. Wir konnten das mit keinem normalen SQL basierten DBMS lösen und haben speziell dafür was eigenes mit einer aktuellen Verbeitungsrate von besser 8Mio/Records pro Sekunde bei bis 450Mio Records per Table noch immer mit Delphi2007 in Win32 entwickelt.


=> du wills aber was universelles machen.. ergo schau dir in den Sourcen von FireDac oder UniDac an, wie die das lösen und dann kannst du dich ja entscheiden ob&wie du dein SQlite ManagementTool unter Win32 realisierst

hstreicher 30. Jul 2016 18:41

AW: OutOfMemory Fehler abfangen
 
Google Delphi available memory

da es sich ja um ein 32 Bit Progamm handelt sollte das hier ausreichen

http://www.delphitricks.com/source-c...le_memory.html

oder das hier

http://stackoverflow.com/questions/7...4-gb-installed

Sir Rufo 30. Jul 2016 19:05

AW: OutOfMemory Fehler abfangen
 
Ich kenn das Management Studio vom SQL-Server und dort werden per Default 1000 Zeilen geladen. Will man die nächsten haben, dann drückt man Next Page und es werden die nächsten 1000 geladen.

Halte ich zunächst für völlig ausreichend. Die 1000 kann der Anwender auch noch selber verändern.

Aviator 31. Jul 2016 00:02

AW: OutOfMemory Fehler abfangen
 
Eine Grundeinschränkung von 1000 Zeilen wollte ich sowieso einbauen wenn jemand die Tabelle öffnet. Der User kann die Zahl danach natürlich erhöhen.

Entweder ich mache es so, dass der User selbst schuld ist wenn er so viele Datensätze lädt oder ich muss schauen, wie ich das abgefangen bekomme.

Die Links werde ich mir morgen anschauen. Würde es etwas bringen, vorher einen Count auszuführen und bei einer einstellbaren Zahl von Datensätzen gewarnt wird? Würde wahrscheinlich zu lange dauern und je mehr Stringspalten darin sind, desto früher läuft der Speicher ja auch über.

Aviator 1. Aug 2016 11:31

AW: OutOfMemory Fehler abfangen
 
Gerade noch eine Sache hierzu. Ich könnte dafür zwar ein neues Topic aufmachen, weiß aber nicht ob sich das lohnt, da ich die Antworten nicht kenne.

Ich wollte meine Anwendung sowieso so programmieren, dass ich diese auch als 64-bit Anwendung kompilieren kann. Auf was muss ich genau achten, wenn ich ein Programm sowohl als 32-bit als auch 64-bit kompilieren möchte? Muss ich andere Variablentypen verwenden, ganze Strukturen eines Programmes anpassen oder kann ich überall mit den selben Variablen arbeiten und es funktioniert trotzdem? Möchte natürlich die Vorzüge von 64-bit auch im Bezug auf Integer o.ä. nutzen und nicht nur einen größeren Adressraum für den Arbeitsspeicher haben.

Falls ich hierfür einen neuen Thread aufmachen sollte da das Thema evtl. umfangreicher ist als gedacht, dann sagt einfach bescheid.

Bernhard Geyer 1. Aug 2016 12:26

AW: OutOfMemory Fehler abfangen
 
Zitat:

Zitat von Aviator (Beitrag 1343890)
Möchte natürlich die Vorzüge von 64-bit auch im Bezug auf Integer ... nutzen

Die größe des Integers bleibt aber bei 64-Bit Systemen auch bei 32-Bit.
Und int64 kann man auch auf 32-Bit Systemen verwenden

Aviator 2. Aug 2016 10:48

AW: OutOfMemory Fehler abfangen
 
Zitat:

Zitat von Bernhard Geyer (Beitrag 1343895)
Zitat:

Zitat von Aviator (Beitrag 1343890)
Möchte natürlich die Vorzüge von 64-bit auch im Bezug auf Integer ... nutzen

Die größe des Integers bleibt aber bei 64-Bit Systemen auch bei 32-Bit.
Und int64 kann man auch auf 32-Bit Systemen verwenden

Ja klar. War ja auch nur ein Beispiel. Auf was sollte man achten, wenn man eine 32-bit und eine 64-bit Anwendung im gleichen Projekt abdecken will? Welche Datentypen muss/sollte man verwenden? Muss man mit IFDEF usw. arbeiten oder oder oder?

Ich glaube das führt schon fast zu einem neuen Thread. :cyclops:

sh17 2. Aug 2016 10:52

AW: OutOfMemory Fehler abfangen
 
Wenn man mit Pointer arbeitet, muss man aufpassen, das diese bei win64 8 byte haben. Wenn man die bisher in einem Integer gespeichert hat, geht's schief.


Alle Zeitangaben in WEZ +1. Es ist jetzt 15:02 Uhr.

Powered by vBulletin® Copyright ©2000 - 2022, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2021 by Daniel R. Wolf