C++ Konzept persitente Daten

Das beste Tool für diesen Zweck ist vermutlich eine Timeseries Datenbank. Bei Klimadaten denke ich an einen timestamp und dann ein paar ints/floats pro Timestamp an Messungen. Das ist der Anwendungszweck für den diese spezielle Art von Datenbank gemacht ist, und die können diese Daten dann auch sehr gut komprimieren. Wenn aber eine normale SQL Datenbank hier schon nicht gewünscht ist, ist das vermutlich zu komplex.

Die Variante die am wenigsten Änderungen erfordert ist die Daten komprimiert auf der Platte zu speichern, und einfach on-the-fly entpacken während sie in den RAM geladen werden. Die Daten wären dann im RAM entpackt, d.h. der Rest des Programms bleibt unverändert.

Bei den Kompressionsalgorithmen würde ich nicht lange überlegen, nimm einfach den der am einfachsten zu implementieren aussieht, so groß sind die Unterschiede nicht. Und bei Compression settings etwas niedriges wählen, stärkere Kompression braucht praktisch immer sehr viel mehr Zeit und bringt relativ wenig.
 
  • Gefällt mir
Reaktionen: KitKat::new() und NJay
Bezgl. der Komppression werf ich mal die Möglichkeit in den Raum, das nicht über deine Software zu machen, sondern über Windows' Full-Drive Compression oder in Linux ein "Compressed Filesystem".

Zu der Sache mit der DB wurde dir ja schon intensiv geraten, da brauch ich nicht noch mehr drauf herum reiten. ;) :D

Ich will dazu aber noch etwas sagen:
SQLite ist eigentlich eine Bibliothek für den "perfektionierten, optimierten und glorifizierten" Dateizugriff mit nützlichen Datenbankelementen und Zugriff per SQL.
Teile davon könntest du auch nachbauen, im Besonderen das Anhängen von Daten am Ende der Datei (was noch relativ einfach ist) oder das partielle Öffnen von Dateien per Offset.
Das würde verhindern, dass du das komplette File in den Speicher einlesen musst, birgt aber die Notwendigkeit, dir sowas wie einen Index anzulegen und zu Verwalten, in dem du nachschauen kannst, wo was steht.

Das geht alles auch mit asynchronem Zugriff, ist generell aber recht aufwändig zu implementieren.
Fürs Anhängen von Kontent am Ende einer Datei (oder jede andere beliebige Stelle) in Windows siehe WriteFile bzw. WriteFileEx und dort im Detail jeweils den Part bei "lpOverlapped".
Für partielles Öffnen in Windows siehe ReadFile bzw. ReadFileEx und dort im Detail jeweils den Part bei "lpOverlapped"..
(Unter Linux kenne ich mich diesbezüglich nicht aus, gibt es dort aber natürlich auch.)
 
Zuletzt bearbeitet: (Ein [/URL] zu viel...)
Hm.. Ich muss meine Aussagen ein Stück weit revidieren. 😕

Da SQLite nur einen eingeschränkten Umfang an Datentypen bietet, wird eine Fließkommazahl stets mit 8 Byte abgespeichert. Es gibt nicht wie zB im MS SQL-Server einen Datentyp mit 4 Byte. Dadurch wird also bei Zahlen wie "1.123456" nichts eingespart, weil es unerheblich ist ob das nun ~8 Zeichen eines Strings sind oder als 8-Byte-Float gespeichert wird. Bei einem banalen Test mit Id, Timestamp und 5 Fließkommazahlen kam ich bei einer SQLite-DB in etwa auf dieselbe Größe wie bei einer CSV. Das Bild könnte sich aber deutlich verschieben, wenn statt Fließkommazahlen Integer verwendet werden, weil diese auch in SQLite in allen Ausprägungen von 1 - 8 Byte vertreten sind, abhängig von der Größe der Zahl, während in der CSV je Zeichen ein Byte draufgehen würde.


Dennoch lässt sich feststellen, dass ein Update (Suchen und Ersetzen) auf meine Test-Tabelle mit 1 Million Datensätzen, im DB Browser für SQLite knapp 2,6 Sekunden dauerte, während Notepad++ mehrere Minuten keine Reaktion zeigte bis dann plötzlich das Ergebnis da war. Hier sieht man den Unterschied im Zugriff, weil SQLite trotz seiner eher rudimentären Datenbankengine vergleichsweise umfangreiche und performante Zugriffsmöglichkeiten bietet.


Fazit:

Speicherplatzersparnis hängt direkt mit den verwendeten Datentypen und deren Inhalten zusammen. Die Performance sollte jedoch in nahezu jedem Fall überlegen sein. Insbesondere dann, wenn komplexe Abfragen wie zB eingrenzende Zeitstempel verwendet werden, hat man mit SQL-Syntax einfach das mächtigere Werkzeug an der Hand als wenn man dies aufwändig und mutmaßlich umständlich selbst programmiert.
Geht man gar über die reine Abfrage der Daten hinaus und möchte auch Daten ändern, wird man bei einer CSV vor die Wand laufen, weil man effektiv die komplette Datei neu schreiben muss. Das übernimmt bei einem Datenbanksystem, inkl. SQLite, der Treiber, der dann durchaus gezielte Änderungen in der Datenbank vornehmen kann.

Selbst wenn Datenänderungen im aktuellen Szenario kein Thema sind, empfiehlt es sich einheitlich zu bleiben und sich an gängige Standards zu halten. Das schließt eine Datenbank zur Speicherung und Verwaltung von Daten mit ein. Zum einen gewinnt man ja mutmaßlich an Performance bei der Abfrage und zum anderen muss man sich keine Zugriffsmethoden mehr ausdenken. Auch bei DB-Tabellen mit unsagbar vielen Einträgen kann man Blockweise Daten abrufen und sie im Arbeitsspeicher zwischenspeichern, um mit ihnen zu arbeiten. Das ist gar nicht so ungewöhnlich und wird beispielweise gerne gemacht, wenn man in einem Datagrid eben diese riesigen Tabellen durchscrollen will.

Und wenn im nächsten Projekt dann auch Daten geändert, hinzugefügt oder gelöscht werden sollen, lässt sich dies mit einer Datenbank auf ganz ähnliche Weise erledigen wie der Abruf. Bei einer CSV müsste man sich dann abermals Funktionen programmieren, die das möglichst performant tun, was offen gestanden nicht so einfach werden wird, wenn da Gigabyte-Dateien dranhängen...
 
  • Gefällt mir
Reaktionen: NJay
Treppenwitz bei der Sache:
Mach mal weiter, programmier Dein System aus CSV-Dateien mit Kompression und Binärdaten zuende, pack noch ne Indizierung darauf, mach die Chose konfigurierbar, schaffe dafür eine Client-Bibliothek und, kaum hast Du 200 Stunden investiert, hast Du Deine eigene proprietäre Datenbank-Engine erschaffen!
Ich stimme dem Grundtenor hier zu, ich würde hier auch mit einem DBMS arbeiten (aber: Wenn Dein Werkzeug ein Hammer ist, sieht jedes Problem aus wie ein Nagel...). Letztlich musst Du selber wissen, wie Du Deine Daten verarbeiten willst, aber aktuell erfindest Du das Rad halt neu.
+1 Zeitreihendatenbank
 
  • Gefällt mir
Reaktionen: KitKat::new() und NJay
Danke für den ganzen Input 👍, also werd mich auf jeden Fall nochmal mit Datenbanken beschäftigen.
Was SQLite angeht, man kann auch als blob speichern und reinpacken was man will. Zum Startpunkt Suchen müsste dann aber die erste Spalte der Zeitstempel sein, zweite Spalte könnte man als blob den Rest binär reinpacken.
Was @Bagbag schreibt zeigt aber schon mal, dass ich was mein low-level Ursprungsansatz angeht nicht ganz auf dem Holzweg war. zstd von facebook stach in meinem Link nicht so heraus aber hat in anderen Tests eine extrem gute Ratio bzw in vielen Bereichen die Beste.
 
T_55 schrieb:
Was SQLite angeht, man kann auch als blob speichern und reinpacken was man will.
Ja, kann man. Aber dann musst du halt auch wieder in deinem Programmcode die (de)serialisierung vornehmen. Wenn man die "normalen" Datentypen nimmt und alles in eigene Spalten packt, kann man sich das sparen. Suchen und filtern auf blob Daten fällt natürlich auch raus.
 
T_55 schrieb:
Zum Startpunkt Suchen müsste dann aber die erste Spalte der Zeitstempel sein, zweite Spalte könnte man als blob den Rest binär reinpacken.
Der Primary Key ist immer eindeutig, ergo am besten eine fortlaufende Zahl (um die Eindeutigkeit kümmert sich die DB selbst). Ein Zeitstempel ist keineswegs eindeutig sein. Der Zeitstempel ist höchstens ein Index, damit der Zugriff schneller stattfinden kann. Und verabschiede dich bitte von BLOBs, die sollten in DBs nie verwendet werden (außer man hat einen ganz bestimmten Use-Case). Ein paar Zahlen zu speichern ist keiner davon.

Wie gesagt: Arbeite dich erstmal in die Datenmodellierung und SQL selbst ein. Optimieren kannst du später. Eine verfrühte Optimierung tritt dir ggf. später mit kräftigem Anlauf in den Hintern.
 
benneq schrieb:
Suchen und filtern auf blob Daten fällt natürlich auch raus.
This.

Im Eingangspost war ja allgemein von Klimadaten die Rede. Nehmen wir also mal an, dass es sich um Temperaturen handelt. Nu kann man natürlich definieren, dass man stets nur einen zeitlichen Bereich betrachten möchte, also zB die Daten der letzten 24h. So wäre es egal was im blob drinsteht, weil man sich mittels SELECT die Einträge holt, um sie dann zu bearbeiten und den blob zu deserialisieren. Aber was ist nun, wenn man aus den Klimadaten nur eben jene abrufen möchte, bei denen die "Temperatur Technikraum" >30 °C ist, unabhängig vom Zeitfenster?

SELECT * FROM Klimadaten WHERE TempTechnik>30

Bumm, alle Messpunkte, bei denen der Grenzwert überschritten wurde, schön sortiert in einem Abfrageergebnis. Mach das mal, wenn die fragliche Temperatur irgendwo im blob codiert ist. Dann darfst du das nämlich wieder alles von Hand machen was die Datenbankengine dir ja gerade abnehmen soll.

Selbst wenn man jetzt noch zusätzlich ein Zeitfenster betrachten möchte, erweitert man eben die WHERE-Klausel um ein Matching für den Zeitstempel.

Der große Vorteil eines Datenbanksystems ist ja gerade der, dass die Daten neutral und unbewertet abgespeichert werden. Es ist einerlei auf Basis welcher Kriterien man die Daten abrufen möchte, weil es in jedem Fall gleich abläuft. Später führt das dann allerdings unweigerlich dazu, dass man sich mit der Form der Tabellen und den Beziehungen untereinander auseinandersetzen muss, um eben diese Neutralität zu wahren. Das nennt sich dann Normalform. In deinem Fall ist das aber vernachlässigbar, wenn dir bisher eine banale CSV ausgereicht hat.
 
Yuuri schrieb:

Das hier ist eher ein Spezialfall bei dem Normalisierung kaum eine Rolle spielt. Zeitreihen sind schon etwas deutlich anderes als jetzt ein typisches relationales Schema.

Bei Zeitreihen ist es vor allem interessant die Daten automatisch zu partitionieren, keine Ahnung ob SQLite das kann. In Postgres wäre z.B. auch sowas wie ein BRIN Index interessant auf dem Timestamp, da man damit sehr billig den richtigen Zeitraum in Abfragen finden kann. Insgesamt wäre aber eine echte Zeitseriendatenbank vermutlich nochmal deutlich besser.

Meine Vermutung ist das eine relationale Datenbank nicht viel Platz spart, auch wenn sie natürlich sehr viele Vorteile bei Queries hat. Eine relationale Datenbank kann die besondere Struktur von Zeitserien nicht ausnutzen und muss mit relative viel Overhead speichern.
 
  • Gefällt mir
Reaktionen: KitKat::new()
Yuuri schrieb:
Ein Zeitstempel ist keineswegs eindeutig sein.
Das kannst du ohne weitere Infos gar nicht wissen. Sie können eindeutig sein, oder auch nicht.
Yuuri schrieb:
Wie gesagt: Arbeite dich erstmal in die Datenmodellierung und SQL selbst ein.
Warum auf Basis von SQL statt Zeitreihen?
Yuuri schrieb:
außer man hat einen ganz bestimmten Use-Case
du müsstest noch ergänzen was für einen Use Case: Er hat nämlich einen bestimmten Use Case
 
Dalek schrieb:
Das hier ist eher ein Spezialfall bei dem Normalisierung kaum eine Rolle spielt.
Wieso? Ich kenn jetzt Influx nicht, aber wenn ich dort Daten als
Code:
"1,2,3,4,5" // string
statt (veranschaulicht in JSON)
Code:
{
  n1: 1,
  n2: 2,
  n3: 3,
  n4: 4,
  n5: 5
}
speicher, hab ich ohne Logik Layer oben drauf keinen Zugriff auf die Einzelwerte. Die Normalform sagt ja nur aus, dass man Werte atomar speichern soll. Eine Anschrift wie
Code:
Platz der Republik 1, 11011 Berlin
kann ich nicht sinnvoll via DB filtern. Es war ja genau auf das Beispiel
Raijin schrieb:
SELECT * FROM Klimadaten WHERE TempTechnik>30
bezogen.
Dalek schrieb:
Bei Zeitreihen ist es vor allem interessant die Daten automatisch zu partitionieren, keine Ahnung ob SQLite das kann.
SQLite unterstützt natürlich kein Partitioning. Die gesamte Datenbank ist eine Datei. Eine Tabelle somit über mehrere Dateien splitten geht nicht. Wenn SQLite nicht mehr reicht, kann er problemlos auf MySQL/Postgres/SQL Server/whatever migrieren und ggf. dessen Optimierungen ausnutzen.
KitKat::new() schrieb:
Das kannst du ohne weitere Infos gar nicht wissen.
Doch, problemlos. Ein Event kann ohne Weiteres zum gleichen Zeitpunkt stattfinden. Oder soll ich noch Millisekunden mit aufzeichnen? Was wenn die identisch sind - Nanosekunden? Picosekunden? ...? Im Endeffekt ist die Auflösung nur so genau, wie die beteiligten CPUs mir Daten liefern können. Nur weil man sagt, "x tritt nicht auf", muss ich mich nicht von Best Practices lösen. Das hier ist wohl auch kein Fall von Multi-Master-Replication, wo UUIDs eher zusagen (wo auch eine Chance auf Duplikate besteht, die praktisch aber null ist), ergo kann man das anwenden. Krampfhaft einen anderen Typ als PK zu verwenden, zeugt für mich eher von "Ich mach das ganz besonders". Und fünf Tage/einen Monat/ein Jahr später fall ich auf die Gusche, weil mein "intelligentes Tool", mal wieder doch nicht so "intelligent" war und ich bis dahin alles refactoren darf. Nicht nur die Anwendung selbst, sondern ggf. auch alle Anwendungen, die darauf aufbauen. Ein schönes Schlamassel ist buchstäblich vorprogrammiert.

Nimm einfach nen AI PK und der Timestamp ist einfach ne Spalte mit entsprechendem Index. Da kann auch zeitgleich ein anderes Event passieren (Aus nem Datenbestand auf nem anderen Server? Selber Server, anderer Prozess/gleiche Anwendung?), was später ggf. zusammengeführt wird. Die IDs kann ich dann problemlos neu zuweisen, mit dem Timestamp als PK muss ich diesen dann nochmal überarbeiten und ein "anderes System" für den PK finden.

(...wenn ich noch eine Tabelle mit Compound PK außerhalb von Relationstabellen sehe, dreh ich durch. Da war auch jemand "ganz schlau"...)
KitKat::new() schrieb:
Warum auf Basis von SQL statt Zeitreihen?
Hier gehts die ganze Zeit um Datenbanken, was im klassischen Sinne SQL impliziert. Kurz darauf wurde NoSQL, danach Influx eingeworfen, wobei letzteres auch SQL-artig ist. Bitte nicht krümelkacken... Der TE hat mit SQL zudem noch nicht gearbeitet...
KitKat::new() schrieb:
Er hat nämlich einen bestimmten Use Case
Drei Zahlen mit einem Timestamp als Datensatz speichern. Wo braucht man hier einen BLOB? Auch funktioniert obiges Beispiel wieder nicht mehr. Ein Cluster setzt er mit hoher Wahrscheinlichkeit nicht auf.
 
Ich versteh hier nicht, wieso es als Binärdaten abgelegt werden sollte? Klar kannst sogar die 10GB CSV's als Binärdaten in der SQLite DB ablegen, abfragen und dann wieder auslesen, aber macht ja alles keinen Sinn, wenn die wirklich relevanten Daten, ein paar Floats und villt nen String oder so sind.

Vor allem lässt die Performance dann gewaltig zu wünschen übrig. Vielleicht ist auch mal interessant zu wissen, was mit den Klimadaten geschehen soll, oder geschieht?
 
  • Gefällt mir
Reaktionen: RalphS
Yuuri schrieb:
Code:
Code:
{
  n1: 1,
  n2: 2,
  n3: 3,
  n4: 4,
  n5: 5
}
speicher, hab ich ohne Logik Layer oben drauf keinen Zugriff auf die Einzelwerte. Die Normalform sagt ja nur aus, dass man Werte atomar speichern soll.

Da war ich etwas unpräzise, ich bin davon ausgegangen das du die Normalisierung bis zur dritten Normalform meinst. Die erste Normalform macht hier sicher noch Sinn, und wenn es mit eine SQL Datenbank gemacht werden sollte dann würde ich das auch auf jeden Fall so machen und eine Column pro Messgröße und eine Row pro Messpunkt nehmen.

Wobei es da tatsächlich Ausnahmen gibt wo auch die erste Normalform ignoriert wird. Ich habe das bei einem Projekt gesehen das Zeitreihen in Postgres schreibt und mehrere Werte pro Row schreibt in einem Array damit die Insert Performance besser ist. Das ist aber jetzt wirklich untypisch und nichts was man nachmachen sollte außer man weiß da sehr genau was man macht.
 
Bin überascht, wie einheitlich hier alle "pro DB" sind.
Wenn die Berechnungen auf Daten, welche im RAM liegen, nicht super lange dauern, sondern vor allem viel lesend und schreibend gearbeitet wird - dann halte ich ne DB nicht pauschal für optimal. Dafür sind imho noch zu wenige usecases bekannt und DBs sind extrem viel langsamer als zB eine std::map, wenn das ganze nur 1D (Also zB nach Zeit) sortiert verwendet wird. Auch gibts ja multi-dimensionale Datenstrukturen mit https://de.wikipedia.org/wiki/R-Baum

Wenn DBs dann am Ende nur zur de- serialisierung verwendet werden, von so extrem einfachen Datentypen, finde ich es auch overkill dafür ne DB zu nutzen und würde maximal einfach mit https://www.boost.org/doc/libs/1_73_0/libs/serialization/doc/tutorial.html arbeiten.
Oder ist der Vorschlag nichts mehr im RAM zu haben und komplett in der DB zu arbeiten? Da würde mich nicht wundern, wenn Performance über 100x schlechter ist als zuvor mit C++-Containern.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Miuwa und BeBur
@kuddlmuddl
Es geht nicht darum die Daten nicht mehr im RAM zu haben. Sondern um das Laden der Daten vom Datenträger in den RAM und das persistieren dieser auf den Datenträger.

Es werden offenbar nicht immer alle Daten gebraucht, deshalb will er ja das mit dem file-seek.

Für jede Berechnung immer erst ein SELECT zu machen, weil die DB als "RAM" genutzt wird... Das wäre ja katastrophal für die Performance, aber ist hier ja nicht der Tenor.
 
  • Gefällt mir
Reaktionen: Raijin und kuddlmuddl
Klimadaten kommen oft in relativ spezialisierten Dateiformaten daher, z.B. netCDF (etwa die E-OBS Daten) oder Raster-files. Diese Formate decken einige deiner gewünschten Funktionen direkt ab. Aus Kompatibilitätsgründen zu Datenquellen kann es sich daher anbieten, dasselbe Format auch 'intern' zu verwenden.
 
Datenbanksysteme haben alle ihre Vor- und Nachteile und wenn sich für den vorliegenden Fall ein anderes DBMS als SQLite im Speziellen bzw. SQL-DBs im Allgemeinen anbietet, dann ist das natürlich auch legitim.

Ich persönlich arbeite grundsätzlich nur mit SQL-Datenbanken und kann daher auch nur auf dieser Basis urteilen.. Fakt ist, dass aktuell eine CSV-Datei zum Einsatz kommt, also eine reine 2-dimensionale Tabelle mit Zeitstempel und ein paar Messwerten ohne jedwede Verknüpfung oder dergleichen. Davon ausgehend ist eine SQL-Datenbank mindestens als gleichwertig zu betrachten, wenn nicht sogar besser durch die umfangreichen Zugriffs- bzw. Abrufmöglichkeiten im Vergleich zur CSV.

Sollte sich darüber hinaus für eben genau diesen Zweck mit den zeitbasierten Messdaten eine andere Datenbank als SQLite, etc. anbieten, ist das vollkommen legitim, wenn das auch empfohlen wird.
 
  • Gefällt mir
Reaktionen: PEASANT KING und NJay
Sei vorsichtig, nicht Probleme zu erschlagen, die du noch garnicht hast. Die Daten selbst binär zu schreiben kostet dich eine Menge Arbeit und das erkaufst du dir durch Komplexität. Wie schon richtig angemerkt, kannst du das umgehen, indem du auf eine Datenbank setzt, die allerdings durch ihre Flexibilität auch komplex sind.

Wenn ich das richtig sehe, ist deine einziges Problem aktuell die Dateigröße. Das kannst du simpel über Kompression lösen. Das kommt effektiv ohne Performancepenalty, da dein Bottleneck die Festplatte, nicht die CPU ist. Nicht ohne Grund gibt es Dateisysteme, die transparent alles komprimieren, und Standardbibliotheken zur Datenverarbeitung bieten on-the-fly Komprimierung bei ihren Ein- und Ausgabefunktionen.
 
Naja, aber am Ende macht er es als Hobby, da kann man schonmal sowas selbst implementieren, vor allem wenn einen das Themenfeld interessiert.
Ob ers dann auch wirklich selber macht, ist ja allein ihm überlassen.
Theoretisch hat er auch mit dem bloßen Versuch nichts verloren. (Außer evtl. freie Entwicklungszeit, die in andere Features investiert hätte werden können.)

Ich habe z.B. bisher nur eigene binäre Dateiformate implementiert und mich würds danach schon reizen einen Threading-fähigen partiellen Lese- und Schreibzugriff für Dateien zu bastlen.
Nur fehlt mir da die Notwendigkeit und damit auch ein Großteil der Motivation...
 
Zurück
Oben