- Wenn OpenDocument Presentation (ODP) statt eines ZIP-Archivs in einem SQLite-Container gespeichert würde, könnten Dokumentenspeicherung, Start und Wiederherstellung sicherer und schneller entworfen werden
- Derzeit ist ODP so aufgebaut, dass XML- und Bilddateien in einem ZIP-Archiv gebündelt werden; eine Präsentationsdatei mit 49 Folien im Beispiel besteht aus insgesamt 78 Einträgen wie
content.xml, styles.xml, meta.xml, settings.xml und Bildern
- Bei einer ZIP-basierten Struktur wird schon bei kleinen Änderungen leicht das gesamte Archiv neu geschrieben, wodurch inkrementelle Updates schwierig sind und Verzögerungen bei File/Save sowie mehr Schreibvorgänge auf SSDs entstehen
- Mit SQLite lassen sich Dateien als Tabellenzeilen speichern; darüber hinaus kann man Inhalte und Versionen pro Folie aufteilen, sodass nur die erste Folie gelesen oder nur geänderte Folien gespeichert werden
- Dies ist weder eine Kritik an OpenDocument noch ein Vorschlag, es zu ändern, sondern ein Beispiel dafür, wie SQLite in Anwendungsdateiformaten atomisches Speichern, Zugänglichkeit, Versionsverwaltung und Wiederherstellungsfunktionen einfacher machen kann
Umfang und Gegenstand des Gedankenexperiments
- Gegenstand ist innerhalb von OpenDocument insbesondere das Präsentationsformat ODP (OpenDocument Presentation)
- Ziel ist nicht, OpenDocument tatsächlich zu ändern, sondern den Einsatz von SQLite als Container bei der Gestaltung künftiger Dateiformate zu prüfen
- Erwartete Vorteile sind kleinere Dokumente, schnelleres File/Save, schnellerer Start, geringerer Speicherverbrauch, Dokumentversionsverwaltung und eine bessere User Experience
Aktuelle ODP-Dateistruktur
- Eine ODP-Datei ist ein ZIP-Archiv, das XML-Dateien und Bildressourcen enthält
- Als Beispiel hat eine 49-Folien-Präsentation zu SQLite von der SouthEast LinuxFest 2014 laut Ausgabe von
zip -l insgesamt 78 Einträge
- Die vier XML-Dateien
content.xml, styles.xml, meta.xml und settings.xml definieren Folienlayout, Textinhalte und Stile
- Die Präsentationsdatei speichert 62 Bilder separat, von Vollbildfotos bis zu kleinen Icons
- Die Datei
mimetype enthält eine einzige Zeile: application/vnd.oasis.opendocument.presentation
- OpenDocument-Dateien für Textverarbeitung und Tabellenkalkulation sind ähnlich aufgebaut, aber analysiert wird hier ODP
Grenzen von ZIP-basiertem ODP
- Ein ZIP-Archiv ähnelt eher einer Key/Value-Datenbank, die für einmaliges Schreiben und vielfaches Lesen optimiert ist, und passt zu einer Struktur mit wenigen Schlüsseln und großen BLOB-Werten
- Da einzelne Einträge schwer zu aktualisieren sind, wird beim Befehl
File/Save üblicherweise das gesamte ZIP-Archiv neu geschrieben
- Man könnte einzelne Einträge zwar so aktualisieren, dass das gesamte Dokument auch bei Stromausfall oder Absturz nicht beschädigt wird, aber das ist schwierig genug, dass es praktisch kaum genutzt wird
- In einer 50-MB-Präsentation kann schon die Änderung eines einzelnen Zeichens dazu führen, dass die gesamten 50 MB neu geschrieben werden
- Die Startzeit kann langsamer werden
- ODP speichert alle Folieninhalte in einer einzigen großen XML-Datei namens
content.xml
- LibreOffice liest und parst diese komplette Datei, um die erste Folie anzuzeigen
- Offenbar werden auch alle Bilder in den Speicher geladen; deshalb erscheint beim Doppelklick auf die Datei zunächst ein Fortschrittsbalken statt der ersten Folie
- Der Speicherverbrauch steigt
- Die ZIP-Struktur fördert Implementierungen, die beim Start das gesamte Dokument in den Speicher laden, dort bearbeiten und beim Speichern das komplette Dokument auf die Festplatte schreiben
- Eine 50-MB-Präsentationsdatei kann mehr als 200 MB RAM belegen
- Wenn mehrere Präsentationen gleichzeitig geöffnet sind und dazu Browser und Desktop-Apps laufen, kann Swapping auftreten
- Die Wiederherstellung nach Abstürzen wird umständlicher
- Anwendungen aus der OpenOffice-Familie sichern das im Speicher befindliche Dokument periodisch, um Abstürze abzufangen
- Während der Sicherung kann die App einige Sekunden einfrieren, und nach dem Neustart ist ein separater Wiederherstellungsdialog nötig
- Die Zugänglichkeit der Inhalte ist gering
- Mit ZIP-Werkzeugen lassen sich Bilder extrahieren, aber Folientexte mit allgemeinen Tools auszulesen oder zu bearbeiten ist schwierig
- In der Beispieldatei enthält
content.xml in der ersten Zeile die XML-Deklaration und in der zweiten Zeile 211.792 Zeichen XML in einer einzigen Zeile
Erste Verbesserung: ZIP durch SQLite ersetzen
- Der erste Schritt ist eine einfache Struktur, bei der ZIP-Einträge durch Zeilen einer SQLite-Tabelle ersetzt werden
CREATE TABLE OpenDocTree(
filename TEXT PRIMARY KEY,
filesize BIGINT,
content BLOB
);
- In diesem Schritt wird die übrige Struktur des Dateiformats nicht verändert
- Es bleibt weiterhin eine „Dateisammlung“, nur dass jede Datei statt eines ZIP-Eintrags nun eine Zeile in einer SQLite-Datenbank ist
- Der Größenvergleich einer SQLite-Datei, die denselben Inhalt wie
self2014.odp von NeoOffice mit dem Dienstprogramm SQLAR neu verpackt, sieht so aus
self2014.odp: 10.514.994 Byte
self2014.sqlar: 10.464.256 Byte
zip.odp, mit dem Kommandozeilen-zip neu komprimiert: 10.416.644 Byte
- Die SQLite-Datei war etwa 0,5 % kleiner als die von NeoOffice erzeugte ODP-Datei
- Eine mit dem Kommandozeilen-
zip gut komprimierte ZIP-Datei war wiederum etwa 0,5 % kleiner als SQLite
- SQLite-Datenbanken können bei der Dateigröße mit ZIP-Archiven konkurrieren
- SQLite bietet atomische Schreibvorgänge, sodass inkrementelle Änderungen gespeichert werden können, ohne das Risiko einer Dokumentbeschädigung bei Absturz oder Stromausfall
- Die Einschränkung, dass
content.xml weiterhin vollständig neu geschrieben werden muss, bleibt bestehen
- Trotzdem können die übrigen 77 Dateien unverändert bleiben, was File/Save beschleunigt und SSD-Schreibvorgänge reduziert
Zweite Verbesserung: Inhalte in kleinere Teile aufspalten
- Da SQLite nicht nur große Blöcke, sondern auch viele kleine Teile effizient speichern kann, lässt sich eine Inhaltstabelle pro Folie anlegen
CREATE TABLE slide(
pageNumber INTEGER,
slideContent TEXT
);
CREATE INDEX slide_pgnum ON slide(pageNumber);
- Um die erste Ansicht anzuzeigen, muss die Anwendung nur die erste Folie lesen
SELECT slideContent FROM slide WHERE pageNumber=1;
- In dieser Struktur lässt sich nur der Inhalt der ersten Folie schnell laden, parsen und anzeigen, ohne beim Start die komplette
content.xml lesen zu müssen
- Dadurch ergeben sich auch mehr Implementierungsoptionen
- Nach dem Anzeigen der ersten Folie können die übrigen Seiten in einem Background-Thread geladen werden
- Es kann nur die aktuelle Folie im Speicher gehalten werden
- Für schnelle Übergänge können auch nur die aktuelle und die nächste Folie im Speicher bleiben
- Auch beim Speichern müssen nur geänderte Seiten neu geschrieben werden, wodurch File/Save schneller wird
- Bei kurzen Textfragmenten sinkt die Kompressionseffizienz, was die Dokumentgröße erhöhen kann
- Da der Großteil des Speicherplatzes eines Dokuments jedoch von Bildern belegt wird, kann die geringere Kompressionseffizienz bei Text als kleiner Preis für die verbesserte User Experience gelten
Dritte Verbesserung: Versionsverwaltung
- Wenn Folien als einzelne Objekte gespeichert werden, lässt sich eine Versionshistorie innerhalb desselben Dokuments ablegen
CREATE TABLE slide(
slideId INTEGER PRIMARY KEY,
derivedFrom INTEGER REFERENCES slide,
content TEXT
);
CREATE TABLE version(
versionId INTEGER PRIMARY KEY,
priorVersion INTEGER REFERENCES version,
checkinTime DATETIME,
comment TEXT,
manifest TEXT
);
- Jede Folie hat nicht mehr eine Seitenzahl, sondern eine eindeutige
slideId; die Reihenfolge wird über eine Liste von slideId-Werten im manifest der Tabelle version festgelegt
- Beim Start wählt die Anwendung zunächst die anzuzeigende Version aus, normalerweise die neueste
SELECT manifest, versionId FROM version ORDER BY versionId DESC LIMIT 1;
- Es ist auch eine Abfrage möglich, die die neueste Version anhand von
checkinTime holt
SELECT manifest, versionId, max(checkinTime) FROM version;
- In SQLite liefert die obige
max(checkinTime)-Abfrage ein definiertes Ergebnis, während viele andere SQL-Datenbanken ein undefiniertes Ergebnis liefern oder einen Fehler ausgeben würden
- Wenn der Benutzer
File/Save ausführt, können nur die geänderten Folien als neue Zeilen in die Tabelle slide eingefügt und eine neue Zeile in version mit einem aktualisierten manifest erzeugt werden
- Die Tabelle
version speichert Check-in-Zeit, Benutzerkommentar und Elternversion und bewahrt so die Änderungshistorie
- Es wäre auch möglich, mehrere Präsentationen innerhalb desselben Dokuments zu speichern
- Statt einer separaten Sicherungsdatei könnte eine spezielle
pending-Version verwendet werden, um ungespeicherte Änderungen häufig und unauffällig zu protokollieren
- Da nicht das gesamte Dokument, sondern nur die Änderungen geschrieben werden, handelt es sich nicht um einige MB, sondern um einige KB
- Die Speicherzeit könnte im Millisekundenbereich statt bei mehreren Sekunden liegen
- Selbst nach einem Neustart infolge eines Absturzes könnte der Großteil oder fast die gesamte Arbeit des Benutzers erhalten bleiben
- Wenn der Benutzer ungespeicherte Änderungen verwerfen möchte, kann er einfach zu einer früheren Version zurückkehren
Weitere mögliche Funktionen in einem SQLite-Dateiformat
- Schon mit nur drei Tabellen kann ein SQLite-Container wichtige Funktionen zu einem Anwendungsdateiformat hinzufügen
- Zusätzlich lassen sich Schema, Indizes, Trigger, Views und Constraints nutzen, um Performance, Komfort und Konsistenz zu erhöhen
- Mögliche Erweiterungen sind unter anderem
- Speicherung eines automatischen Undo/Redo-Stacks in Datenbanktabellen, sodass Rückgängig/Wiederholen sogar über frühere Editiersitzungen hinweg möglich wird
- Hinzufügen von Volltextsuche für ein Foliendeck oder mehrere Foliendecks
- Zerlegung von
settings.xml in SQL-Tabellen, damit andere Anwendungen es einfacher anzeigen und bearbeiten können
- Auslagerung der Sprechernotizen jeder Folie in eine separate Tabelle, sodass Third-Party-Apps oder Skripte leicht darauf zugreifen können
- Unterstützung von Präsentationsstrukturen, die über eine einfache lineare Folienreihenfolge hinausgehen und je nach Publikumsreaktion unterschiedliche Pfade und Umwege erlauben
Häufige Vorbehalte gegenüber SQLite und Gegenargumente
- Aufgrund von Erfahrungen mit Enterprise-SQL-Datenbanken kann es Vorbehalte geben, SQLite als Anwendungsdateiformat zu verwenden
- Viele Enterprise-Datenbanken empfehlen, große Strings oder BLOBs nicht in der Datenbank, sondern in separaten Dateien zu speichern, aber SQLite ist anders
- SQLite kann in jeder Spalte Strings oder BLOBs von bis zu etwa 1 GB speichern
- Bei Strings und BLOBs unter 100 KB ist die I/O-Performance besser als bei separaten Dateien
- Auch die Vorstellung, jedes SQL-Schema müsse in Dritter Normalform (3NF) vorliegen und nur kleine primitive Typen speichern, kann einschränkend wirken
- Relationale Theorie ist wichtig, aber in realen Dateiformaten kann es eine zulässige Wahl sein, komplexe Informationen wie XML oder JSON in Textfeldern zu speichern
SQLite als Anwendungsdateiformat
- SQLite-Datenbankdateien haben nahezu dieselbe Größe wie ZIP-Archive mit denselben Informationen und können in manchen Fällen sogar kleiner sein
- Dank atomischer Updates lassen sich kleine Änderungen sicher in Dokumente schreiben, was Festplatten-I/O reduziert und die Performance von File/Save verbessert
- Anwendungen können nur die für die erste Ansicht benötigten Inhalte lesen und so die Startzeit verkürzen
- Es muss nur der Inhalt im Speicher gehalten werden, der für die aktuelle Anzeige relevant ist; der Rest kann auf dem Datenträger bleiben, was den Speicherverbrauch deutlich senkt
- SQL-Schemata können Informationen direkter und kompakter ausdrücken als eine Key/Value-Struktur wie ZIP
- Das verbessert die Zugänglichkeit für Third-Party-Apps und Skripte
- Fortgeschrittene Funktionen wie eingebaute Dokumentversionsverwaltung und Wiederherstellung von Arbeit nach Abstürzen lassen sich leichter umsetzen
- OpenDocument ist bereits ein etabliertes und gut entworfenes Format, und da SQLite später als OpenDocument entstanden ist, ist dies keine Kritik an der damaligen Entscheidung
- Das Dokument Application File Format bietet weitere Ideen zur Verwendung von SQLite als Anwendungsdateiformat
1 Kommentare
Meinungen auf Hacker News
Ich entwickle eine App, die SQLite als Dateiformat verwendet.
Weil ich den üblichen Ablauf beibehalten möchte, bei dem sich die Datei nur ändert, wenn der Nutzer ein Dokument bearbeitet und dann speichert, kopiere ich sie beim Öffnen in eine
:memory:-Datenbank: https://www.sqlite.org/inmemorydb.htmlDer Nutzer kann nach Belieben Änderungen vornehmen, und die App spiegelt sie ohne separates Dokumentmodell direkt im Datenbankformat wider. Beim Speichern schreibe ich sie mit
VACUUMwieder in die Datenbankdatei zurück: https://www.sqlite.org/lang_vacuum.htmlBei Dateien angemessener Größe funktioniert das gut, und in meiner App bleiben sie immer innerhalb dieses Bereichs.
Besser wäre es, direkt automatisch in der Datenbank zu speichern und den Speichern-Button abzuschaffen. Das ist robuster gegenüber Abstürzen, mit nur einer Datenbank gibt es weniger Code und weniger Bugs, und SQLite-Schreibvorgänge gelingen oder schlagen fehl, ohne Zwischenzustand. Dagegen kann, wie im Dokument zitiert, bei
VACUUM INTOdie Ausgabe-Datenbank bei unerwartetem Beenden oder Stromausfall unvollständig oder beschädigt sein.Wenn man SQLite so verwendet, wie es ursprünglich gedacht war, muss man sich für die Lebensdauer von SQLite darüber keine Gedanken machen.
Viel besser wäre es, nach jeder Aktion an einem temporären Ort zu speichern, etwa nach XDG-Verzeichnissen unter
~/.local/share/application/yourapp, und die Datei an den gewünschten Ort zu kopieren, wenn der Nutzer auf Speichern klickt. Wenn man die App nach einem Stromausfall wieder öffnet, kann man fast an derselben Stelle wiederherstellen und verliert höchstens die letzten paar Sekunden.Wenn der Nutzer speichert, führt man einen Checkpoint aus und führt den WAL-Inhalt in die Hauptdatenbank zusammen.
VACUUMden Inhalt in eine temporäre Datenbankdatei und überschreibt dann das Original; beim Überschreiben verwendet es wie eine normale Transaktion ein Rollback-Journal oder WAL. Daher wird ungefähr bis zum Doppelten der Größe des Originals an freiem Speicher benötigt.VACUUM INTOverwendet statt einer temporären Datenbank die beiINTOangegebene Datei und lässt den Schritt des erneuten Kopierens über das Original weg. Entscheidend ist, ob tatsächlich das auch gegen Stromausfälle robusteVACUUMverwendet wird oderVACUUM INTO, das während des Schreibens anfällig für Stromausfälle ist und bei einem bestehenden Dateinamen offenbar Schäden verursachen kann.Das Problem mit SQLite ist, dass es kein standardisiertes Dateiformat ist.
Es ist gut dokumentiert und weithin verstanden, aber kein ISO-Standard definiert im Detail, wie eine SQLite-Datei zu interpretieren ist. Für alternative Implementierungen gilt dasselbe.
Zip und XML haben eine viel kleinere API-Oberfläche als SQLite. Die API von SQLite geht über ein paar C-Funktionen hinaus und umfasst die SQL-Sprache selbst; einen SQL-Parser, Query-Optimizer, Compiler, eine Bytecode-VM, eine Volltextsuchmaschine usw. ohne Datenbeschädigung zu implementieren, ist ein deutlich größeres Unterfangen als ein XML-Parser.
Für domänenspezifische, geschlossene Apps, bei denen Interoperabilität oder ISO-Standardisierung nicht wichtig sind, ist SQLite ein gutes Dateiformat, aber ich verstehe, dass es bei OpenOffice tatsächlich solche Bedenken gab.
Auch die SQLite-C-Bibliothek ist Public Domain, der Quellcode ist vollständig offen, sie verarbeitet das Dateiformat, und die Dokumentation ist besser als bei den meisten ISO-Standards. Bindings für die wichtigsten Sprachen gibt es ebenfalls fast alle.
Wenn das Problem darin besteht, dass ein OpenDocument-Format, das innerhalb einer SQLite-Datei gespeichert wird, erst noch erstellt und dokumentiert werden muss, ist das etwas anderes. ISO-Standards sind gut, aber wenn man warten müsste, bis ISO ein Dateiformat definiert, gäbe es sehr wenig, was man verwenden könnte.
Genauso wenig muss man alle Tabellenkalkulationsfunktionen implementieren, um eine LibreOffice-Tabelle zu lesen. Nötig ist die Fähigkeit, die Tabellen zu rekonstruieren; die gewünschten Informationen kann man anschließend mit imperativem Code in der Sprache der Wahl durchlaufen und gewinnen.
Diese Bibliothek neu zu implementieren wäre eine große Aufgabe, aber von derselben Art wie die Neuimplementierung von Code, der das OpenDocument-Dateiformat verwendet. Das Dateiformat selbst ist ziemlich einfach.
Wenn Kompatibilität Sorgen bereitet, kann man das Dokument so gestalten, dass es auch über eine andere Datenbank wie MySQL zugänglich ist.
Ich hatte erwartet, dass Audacity mit der Einführung von SQLite seine Dateispeicherfunktion deutlich verbessern würde, aber in der Praxis gab es viele Fallstricke.
Unter Linux schlug das Speichern als neue Datei auf einem per
/etc/fstabeingebundenen NTFS-Mount fehl, der zwar root gehörte, aber für alle beschreibbar war, offenbar wegen eines Berechtigungsfehlers; das Speichern in eine bestehende Datei funktionierte dagegen problemlos.Sobald man ein Projekt bearbeitet, wird die Datei auf der Festplatte verändert. Wenn man ein Audacity-Projekt als Binärklumpen in Git ablegt, entstehen dadurch unnötige Git-Diffs. Selbst nach dem Speichern bleiben veraltete oder gelöschte Daten bis zum Schließen des Projektfensters in der SQLite-Datei; wenn man das Fenster vor dem Commit nicht schließt, können sie ins Repository gelangen. Ich erinnere mich, dass man früher die
.aup3-Datei direktVACUUMen musste, heute reicht es, das Fenster zu schließen. Es erinnert an Fast Save in Word 2003.Wenn ein Projekt, das nur ein paar Hundert MB groß sein sollte, mehrere GB groß wurde und man Speicherplatz sparen musste, war Mix and Render bei einfachen Einspur-Arbeiten die Lösung. Es änderte den Ton nicht, konnte aber beim Speichern und Beenden den Müll bereinigen.
Das ist kein Problem von SQLite selbst, sondern eindeutig ein Problem der Anwendungsschicht. Audacity 2 hatte, glaube ich, ein Konzept für temporären Arbeitsbereich; Audacity 3 scheint die
.aup3-Datei selbst als Arbeitsbereich zu verwenden.Ich habe mir das Audacity-3-Format angesehen: Die Projektdaten, die früher der
.aup-Datei entsprachen, werden als XML in einer Tabelle mit einer einzigen Zeile gespeichert, aber nicht als Klartext, sondern mit einem einfachen Wörterbuch-Coder kodiert. Das fand ich sehr merkwürdig. Es macht Interoperabilität und Prüfung deutlich schwieriger, schadet der Performance zumindest ein klein wenig und die Platzersparnis dürfte bei Audiodateien von Hunderten MB nur ein Rundungsfehler von ein paar KB sein.Aus Git-Sicht ist ein Textformat vorteilhaft, bei dem Diffs und Merges einfach sind. Ich weiß nicht, wie gut ein SQLite-Dump in dieser Hinsicht funktioniert.
Wenn es wichtig ist, kann man es manuell reparieren, aber meistens funktioniert es wieder, wenn man die Datei wegwirft.
Guter Artikel. Allerdings gefällt mir, dass OpenDocument aus einem Bündel von XML-Dateien in einem Zip-Archiv besteht.
Auch ohne schwere Bibliothek, die das Dokumentformat kennt, kann man ziemlich einfach Dokumente wie Spreadsheets erzeugen.
Manchmal möchten Nutzer eines Webdienstes die als Tabellenzeilen exportierten Daten in verschiedenen Tools verwenden. UTF-8-CSV ist offen, konventionell und brauchbar, aber wer Endnutzern schon einmal CSV angeboten hat, kennt den Schmerz, wenn Spreadsheet-Apps daran scheitern.
Ich habe ein Beispiel-Spreadsheet als OpenDocuments ODS und als Microsofts XML-Monster OOXML im XLSX-Format gespeichert und mir dann nur die Grundlagen des XML-Formats erschlossen. Ich habe das Zip-Archiv auf die notwendigen Bestandteile reduziert, Platzhalter für die Inhalte markiert und erzeuge auf Anfrage eine neue Spreadsheet-Datei. Jetzt kann ich dieselben Daten als CSV, ODS, XLSX und JSON ausgeben.
Mit SQLite wäre das auch möglich, aber etwas komplizierter und die Entwicklung langsamer. Dass man in einer Office-Suite ein Vorlagendokument erstellen und dann im XML der gespeicherten Datei herumgraben kann, ist eine nischige, aber gute Funktion.
Besonders problematisch ist Excel in Locales wie
nl_NL, wo es sich so verhält, als sei das Spaltentrennzeichen in CSV-Dateien hart auf Semikolon codiert. Microsoft hat nämlich berüchtigterweise entschieden, dass Niederländer in Dateien mit comma separated values keine Kommas verwenden.localeconv()->decimal_pointab. Ist der Wert,, verwendet Excel sowohl in CSV-Dateien als auch in der Formelsprache Semikolons.Früher konnte man das beim Öffnen von CSV/TXT in Excel konfigurieren, und in LibreOffice geht das immer noch; im Zuge der allgemeinen UI-Vereinfachung wurde es aber irgendwo in das Menü bzw. Ribbon-Tab
Dataverschoben. Man muss eine neue Arbeitsmappe öffnen und die richtigen Optionen finden; wenn man Zeit sparen will, ist LibreOffice die bessere Wahl.Dieser Teil hat mich wirklich überrascht. Kaum zu glauben, dass keine verschachtelte Abfrage nötig ist.
SELECT manifest, versionId, max(checkinTime) FROM version;In SQLite funktioniert diese zweite Abfrage mit
max(checkinTime)tatsächlich gut und liefert eine definierte Antwort. Andere SQL-Datenbank-Engines liefern entweder eine undefinierte Antwort oder einen Fehler, aber SQLite gibtmanifestundversionIddes Eintrags mit dem größtencheckinTimezurück.In diesem Fall braucht man keine verschachtelte Abfrage; man kann nach
checkinTimesortieren und auf einen Eintrag begrenzen:select manifest, versionId, checkinTime from version order by checkinTime desc limit 1Zumindest in SQLite und PostgreSQL sollte das funktionieren. Bei Oracle musste man, soweit ich mich erinnere,
where rownum=1verwenden, weshalb eine verschachtelte Abfrage nötig war.GROUP BY manifest, versionId ORDER BY 3 DESC LIMIT 1betrachten oder für eine CTE, die zuerst den maximalencheckinTimeermittelt und dann joint.Wenn es allerdings mehrere Zeilen mit demselben maximalen
checkinTimegibt, kommt Zufälligkeit ins Spiel; das kann zur Fußangel werden, daher nutze ich solche SQLite3-spezifischen Features ungern. Um deterministisch die beste Zeile auszuwählen, braucht man eine explizite Variante ähnlich der oben genannten.In Postgres kann man mit einer
DISTINCT ON-Abfrage etwas Ähnliches erreichen. Das war eine der Aufgaben, die in SQL einfach aussehen, sich für mich aber am schwierigsten angefühlt haben.manifestundversionIdsind nämlich nicht funktional abhängig vonmax(checkinTime).Zum Beispiel könnten zwei Zeilen denselben
checkinTime-Wert haben und dieser Wert könnte der maximale sein.Ich habe einmal ein Produkt veröffentlicht, das sowohl SQLite als auch XML-Dateien verwendete.
Eine der Verbesserungen bestand darin, einige Tabellen mit wenig Daten in XML-Dateien auszulagern. Die Dateien waren klein und wurden kaum genutzt, wodurch Datenzugriffsschicht und Diagnose einfacher wurden; sie waren als XML mit mehrzeiliger Tab-Einrückung angelegt.
Technische Ansprechpartner, die ein Produkt diagnostizieren sollten, zu bitten, eine SQLite-Datenbank zu öffnen, war eine ziemliche Hürde. In den zentralen Teilen des Produkts war SQLite den XML-Dateien aber klar überlegen. Frühere Versionen verwendeten XML-Dateien, doch XML-Dateien bieten keine gute Möglichkeit für inkrementelle Updates, was zu Skalierungsproblemen führte.
Der Vorteil von XML, ein für Menschen gut lesbares Format zu sein, funktioniert nur dann wirklich, wenn die Datei klein ist und das Schema auf gut lesbares XML ausgelegt wurde. Dass man jedes Mal die gesamte XML-Datei neu schreiben muss, und die mit wachsendem Funktionsumfang entstehende Komplexität schmälern XMLs größten Vorteil sehr schnell.
Es kommt selten genug vor, dass normale Nutzer das Innere von Office-Dokumenten direkt bearbeiten müssen; daher ist es eine akzeptable Einstiegshürde, den Umgang mit einem SQLite-Reader zu lernen. Die Grenzen von XML+Zip bei beliebigen Schreibvorgängen mitten in der Datei lassen sich auch mit Moores Gesetz nicht überwinden.
TEXT- oderBLOB-Felder in SQLite komprimiert werden oder ob angenommen wird, dass der Aufrufer das BLOB vor dem Schreiben komprimiert.ODT wurde mit Blick auf Standardisierung entworfen. Das frühere Format war zwar sehr ähnlich, stützte sich aber stark auf bestehende Standards wie XHTML, SVG und CSS.
Wenn man nicht auf bestehende Standards verweisen kann, würde die ODT-Spezifikation selbst schlagartig riesig werden. Auch der Aufwand, den Standard zu aktualisieren, scheint erheblich zu sein, und in den letzten Jahren gab es kaum Fortschritte.
Realistisch gesehen könnte ein SQLite-Format zwar als Option angeboten werden, aber der Zug für Office-Dokumentformate scheint bereits abgefahren zu sein. Allerdings liefert das ein gutes Argument dafür, die SQLite-Spezifikation als offiziellen Standard auszuarbeiten.
Abgesehen von einigen Schwächen, etwa dem Problem, dass lokale Styles und Textbereiche wegen des Attributs
ooo:rsidexplodieren, nicht dünn besetzten Spreadsheets und dem seltsamen Mechanismus für Tabellen-Styling, ist es ein sehr gut entworfenes Markup für solche Dokumentdaten. Es findet eine gute Balance zwischen semantischem Markup und dem, was Nutzer tatsächlich ausdrücken wollen.Office OpenXML hat dagegen zustandsbehaftete leere Tags für Formatierung, die in DOCX umschalten, ob der nachfolgende Text fett dargestellt wird.
Es riecht irgendwie falsch, ein Dateiformat an SQLite zu koppeln.
SQLite ist gut, aber in diesem Bereich ziemlich einzigartig, weil es so viel leistet und sich deshalb nicht einfach 1:1 nachbauen lässt.
In diesem Fall braucht man aber nicht so viele Funktionen. Grundlegende, sichere Transaktionssemantik und die Fähigkeit, einfache Tabellenstrukturen zu speichern, reichen aus; den kompletten SQL-Standard oder gar einen Query Optimizer braucht man nicht.
Es mag bessere Dateiformate geben, aber besser wäre eines, das von SQLite getrennt ist.
Die Größe liegt unter 1 MB, siehe https://sqlite.org/footprint.html, und selbst mit allen aktivierten Features sind es 750 KB: https://www.sqlite.org/about.html
Zur Compile-Zeit lassen sich etliche Features weglassen, und es scheint auch Optionen zu geben, den Query Planner anzupassen oder zu verkleinern: https://www.sqlite.org/compile.html
Außerdem heißt es: „SQLite konkurriert nicht mit Client/Server-Datenbanken. SQLite konkurriert mit
fopen()“: https://www.sqlite.org/whentouse.htmlLetztlich braucht man nicht die Datenbank selbst, sondern eine Bibliothek, die eine Datenbank-API und entsprechendes Verhalten bereitstellt.
SQLITE_BUSYziemlich mühsam.Mir ist klar, dass bei Transaktionen Serialisierungsfehler zu erwarten sind, aber bei SQLite war es schwer, anhaltende Fehler wie eine Art Selbst-Deadlock von vorübergehenden Problemen durch gleichzeitige Updates zu unterscheiden. Bei einem temporären Fehler kann man die Closure, die die Transaktionsarbeit definiert, erneut ausführen; bei einem dauerhaften Fehler ist das sinnlos.
Ein Teil des Problems ist, dass
sqlite3_stmtsowohl die Eigenschaften eines vorbereiteten Statements als auch eines Result Sets vereint. Man hält es lange, um kompilierten Bytecode zu cachen; wenn man mitten in der Iteration stoppt, kann es zu diesem Zeitpunkt Locks halten. Dadurch können unerwartete Fehlschläge beim Lock-Upgrade entstehen.Am Ende habe ich mit
sqlite3_next_stmt,sqlite3_stmt_busyundsqlite3_sqlein detailliertes Fehler-Reporting gebaut und das Problem beseitigt. Obwohl es nur für den persönlichen Gebrauch war, war der Code für Transaktions-Retries voller optionalem Logging und Kommentaren. Die Retry-Logik für Transaktionen mit PostgreSQL war deutlich einfacher.Eine weitere Überraschung war die Dokumentation, dass im WAL-Modus mit
synchronous=NORMALcommittete Transaktionen nach Stromausfall oder Systemabsturz zurückgerollt werden können: https://sqlite.org/pragma.html#pragma_synchronousFür meine Anwendung war das nicht relevant.
Wenn Richard Hipp und sein Unternehmen einen ISO/IEC/ANSI/ETSI-Standard für SQLite vorweisen, von dem sie garantiert nie abweichen, dazu eine rechtliche Prüfung, dass keine relevanten Patente im Weg stehen, und mehrere kompatible SQLite-Implementierungen, bei denen alle Vorteile erhalten bleiben, dann kann man darüber sprechen, es als Dateiformat zu empfehlen. Andernfalls bedeutet es, eine starke Abhängigkeit von einer Single-Source-Implementierung einzugehen und sie auch den Nutzern aufzubürden.
XML, ASN.1 und JFIF sind offizielle Standards, und auch ZIP ist ein offizieller Standard, der im Zuge der OpenDocument-Standardisierung als ISO/IEC 21320-1:2015 übernommen wurde.
Das Wichtigste bei Dokumenten ist, dass alle anderen sie lesen können. Die Zeit für Disk-Updates zu reduzieren, ist zweitrangig. Man sollte aus Microsofts Versuch, Standardisierungsgremien zu verbiegen, um Lock-in zu erhalten, etwas gelernt haben: https://arstechnica.com/uncategorized/2008/10/norwegian-standards-body-implodes-over-ooxml-controversy/
So falsch kann diese Wahl also nicht sein.
Ein weiteres Beispiel sind Raster-Kartenkacheln. Im Grunde sind das kleine quadratische Bilder, deren Anzahl in die Millionen gehen kann.
Zip, tar, das Dateisystem und SQLite wurden alle ausprobiert, und SQLite war am schnellsten und am kleinsten, sogar besser als allgemeine Archive ohne Overhead.
SQLite hat einen großen Nachteil: Ein BLOB aus der Datenbank kann man nicht
mmapen, sondern muss es an eine andere Stelle kopieren. Eine Zip-Datei kann man, wenn sie unkomprimiert ist oder mit einer speziellen Kodierung wie PVRTC komprimiert wurde, direkt permmapeinbinden.OpenDocument besteht aus komprimierten Bildern und XML. Das bedeutet letztlich, dass das gesamte Format geparst und in den Arbeitsspeicher geladen wird.
Mir ist nicht ganz klar, wie SQLite das verbessern soll. XML ist zwar nicht ideal, aber da es als Zip komprimiert ist, ist auch der Größen-Overhead nicht besonders groß.
Alle Vorteile, die im SQLite-Artikel aufgezählt werden, ließen sich umsetzen, wenn man SQLite als Runtime-Modell des Dokuments verwendet. Das geht sowohl auf der Festplatte als auch im Arbeitsspeicher, aber SQLite muss dafür nicht das Übertragungsformat sein.
Im Gegenteil: SQLite könnte größer werden als das aktuelle Format. Nach Änderungen kann ungenutzter Speicher entstehen, es kann fragmentiert und löchrig werden. Wenn man jedes Mal optimieren muss, verschwinden auch Vorteile wie schnelles Speichern.
Formate, die Delta-Updates und schnelle Index-Lookups benötigen und nicht die gesamte Datei in den Speicher laden sollen, verwenden SQLite tatsächlich häufig als Dateiformat. Allerdings habe ich das Gefühl, dass OpenDocument in diesem hypothetischen Szenario ein schlechtes Beispiel für ein SQLite-Ziel war.
Wenn man SQLite als On-Disk-Format verwendet und die Anwendung korrekt implementiert, muss man am Ende nicht in einem beschädigten Zustand landen.
Mit XML/Zip lässt sich Ähnliches zwar durch einen Umbenennungs-Trick erreichen, aber SQLite bietet das in einer einzelnen Datei auf der Festplatte. Wenn man SQLite ohnehin schon als Speichermodell verwendet, gibt es keinen Grund, es nicht auch als Disk-/Übertragungsformat zu nutzen. An diesem Punkt ist es praktisch gratis.
Das Problem der Dateigröße ließe sich vermutlich mit
VACUUMlösen.