- Si l’on plaçait OpenDocument Presentation (ODP) dans un conteneur SQLite plutôt que dans une archive ZIP, on pourrait concevoir des mécanismes d’enregistrement, de démarrage et de récupération de documents plus sûrs et plus rapides
- Aujourd’hui, ODP regroupe des fichiers XML et des images dans une archive ZIP ; un exemple de présentation de 49 diapositives se compose de 78 éléments au total, dont
content.xml, styles.xml, meta.xml, settings.xml et des images
- Dans une structure fondée sur ZIP, même une petite modification conduit facilement à réécrire toute l’archive, ce qui rend les mises à jour incrémentales difficiles et entraîne des délais lors de File/Save ainsi qu’une augmentation des écritures sur SSD
- En passant à SQLite, les fichiers peuvent être stockés sous forme de lignes de table ; on peut aller plus loin en séparant le contenu et les versions par diapositive, afin de ne lire que la première diapositive ou de n’enregistrer que celles qui ont changé
- Il ne s’agit pas de critiquer OpenDocument ni de proposer de le modifier, mais de montrer, à travers un exemple, que SQLite peut faciliter la mise en place de fonctions d’enregistrement atomique, d’accessibilité, de gestion de versions et de récupération dans les formats de fichiers applicatifs
Portée et objet de l’expérience de pensée
- L’objet est ODP (OpenDocument Presentation), le format de documents de présentation au sein d’OpenDocument
- L’objectif n’est pas de modifier réellement OpenDocument, mais d’examiner, pour de futurs formats de fichiers, l’utilisation de SQLite comme conteneur
- Les bénéfices attendus sont des documents plus petits, un File/Save plus rapide, un démarrage plus rapide, une consommation mémoire réduite, la gestion de versions des documents et une meilleure expérience utilisateur
Structure actuelle des fichiers ODP
- Un fichier ODP est une archive ZIP contenant des fichiers XML et des ressources image
- À titre d’exemple, le fichier d’une présentation de 49 diapositives sur SQLite donnée au SouthEast LinuxFest 2014 contient 78 éléments au total dans la sortie de
zip -l
- Les quatre fichiers XML
content.xml, styles.xml, meta.xml et settings.xml définissent la mise en page des diapositives, le contenu textuel et les styles
- Le fichier de présentation contient 62 images, de la photo plein écran aux petites icônes, stockées sous forme de fichiers séparés
- Le fichier
mimetype contient une seule ligne : application/vnd.oasis.opendocument.presentation
- Les fichiers de traitement de texte et de feuille de calcul OpenDocument ont une structure similaire, mais l’analyse porte ici sur ODP
Limites d’ODP fondé sur ZIP
- Une archive ZIP s’apparente à une base de données clé/valeur optimisée pour une écriture unique et de multiples lectures, et convient aux structures où un petit nombre de clés ont de grandes valeurs BLOB
- Comme la mise à jour d’éléments individuels est difficile, lorsque l’utilisateur choisit
File/Save, toute l’archive ZIP est généralement réécrite
- Il est possible de mettre à jour des éléments individuels sans corrompre tout le document en cas de coupure de courant ou de crash, mais c’est suffisamment difficile pour être rarement utilisé en pratique
- Dans une présentation de 50 Mo, la modification d’un seul caractère peut conduire à réécrire les 50 Mo du fichier
- Le temps de démarrage peut être lent
- ODP stocke tout le contenu des diapositives dans un gros fichier XML unique appelé
content.xml
- LibreOffice lit et parse l’intégralité de ce fichier pour afficher la première diapositive
- Il semble aussi charger toutes les images en mémoire ; par conséquent, lorsque l’on double-clique sur le fichier, une barre de progression apparaît au lieu de la première diapositive
- La consommation mémoire augmente
- La structure ZIP incite à une implémentation qui lit tout le document en mémoire au démarrage, effectue l’édition en mémoire, puis écrit tout le document sur disque lors de l’enregistrement
- Un fichier de présentation de 50 Mo peut utiliser plus de 200 Mo de RAM
- Si plusieurs présentations sont ouvertes en même temps, en plus d’un navigateur et d’applications de bureau, du swapping peut se produire
- La récupération après crash devient contraignante
- La famille OpenOffice sauvegarde périodiquement en arrière-plan le document présent en mémoire pour se prémunir des crashs
- Pendant la sauvegarde, l’application peut se figer quelques secondes, et après redémarrage il faut passer par une boîte de dialogue de récupération séparée
- L’accessibilité du contenu est faible
- Les outils ZIP permettent d’extraire les images, mais il est difficile d’extraire ou de modifier le texte des diapositives avec des outils ordinaires
- Dans le fichier d’exemple, la première ligne de
content.xml est une déclaration XML, et la deuxième ligne contient 211 792 caractères de XML sur une seule ligne
Première amélioration : remplacer ZIP par SQLite
- La première étape consiste en une structure simple qui remplace les éléments ZIP par des lignes d’une table SQLite
CREATE TABLE OpenDocTree(
filename TEXT PRIMARY KEY,
filesize BIGINT,
content BLOB
);
- À ce stade, le reste de la structure du format de fichier ne change pas
- Cela reste une structure de « tas de fichiers », mais chaque fichier devient une ligne dans une base de données SQLite plutôt qu’une entrée ZIP
- La comparaison de taille entre le fichier
self2014.odp créé par NeoOffice et le fichier SQLite réemballé avec l’utilitaire SQLAR donne les résultats suivants
self2014.odp : 10 514 994 octets
self2014.sqlar : 10 464 256 octets
zip.odp, recompressé avec zip en ligne de commande : 10 416 644 octets
- Le fichier SQLite était environ 0,5 % plus petit que l’ODP généré par NeoOffice
- Le fichier ZIP bien compressé avec
zip en ligne de commande était à son tour environ 0,5 % plus petit que SQLite
- Les bases de données SQLite peuvent rivaliser avec les archives ZIP en matière de taille
- SQLite fournit des écritures atomiques, ce qui permet d’enregistrer des changements incrémentaux sans risque de corrompre le document en cas de crash ou de coupure de courant
- La limite consistant à devoir réécrire l’intégralité de
content.xml demeure
- Mais les 77 autres fichiers peuvent rester inchangés, ce qui accélère File/Save et réduit le volume d’écritures sur SSD
Deuxième amélioration : découper le contenu en petits morceaux
- SQLite pouvant stocker efficacement aussi bien de gros blocs que de nombreux petits morceaux, on peut créer une table de contenu par diapositive
CREATE TABLE slide(
pageNumber INTEGER,
slideContent TEXT
);
CREATE INDEX slide_pgnum ON slide(pageNumber);
- Pour afficher le premier écran, l’application n’a qu’à lire la première diapositive
SELECT slideContent FROM slide WHERE pageNumber=1;
- Avec cette structure, il est possible de récupérer, parser et afficher rapidement le seul contenu de la première diapositive, sans lire tout
content.xml au démarrage
- Les choix d’implémentation se multiplient
- Après avoir affiché la première diapositive, le reste des pages peut être lu dans un thread d’arrière-plan
- Une seule diapositive, celle en cours, peut être conservée en mémoire
- Pour accélérer les transitions, on peut aussi garder en mémoire la diapositive courante et la suivante
- L’enregistrement devient également plus rapide, puisqu’il suffit de réécrire les pages modifiées
- Pour de courts fragments de texte, l’efficacité de la compression peut diminuer et faire augmenter la taille du document
- Mais comme les images occupent l’essentiel de l’espace du document, la baisse d’efficacité de la compression du texte peut être considérée comme un coût modeste au regard de l’amélioration de l’expérience utilisateur
Troisième amélioration : gestion de versions
- Si les diapositives sont stockées comme des objets individuels, on peut intégrer un historique de versions dans le même document
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
);
- Chaque diapositive possède un
slideId unique plutôt qu’un numéro de page, et l’ordre est déterminé par la liste de slideId stockée dans le manifest de la table version
- Au démarrage, l’application choisit d’abord la version à afficher et, en général, récupère la version la plus récente
SELECT manifest, versionId FROM version ORDER BY versionId DESC LIMIT 1;
- Il est aussi possible de récupérer la version la plus récente selon
checkinTime
SELECT manifest, versionId, max(checkinTime) FROM version;
- Dans SQLite, la requête
max(checkinTime) ci-dessus renvoie un résultat défini, mais dans beaucoup d’autres bases de données SQL elle peut renvoyer un résultat non défini ou produire une erreur
- Lorsque l’utilisateur effectue
File/Save, seules les diapositives modifiées peuvent être ajoutées sous forme de nouvelles lignes dans la table slide, et une nouvelle ligne version avec le manifest modifié peut être créée
- La table
version enregistre l’heure de check-in, le commentaire utilisateur et la version parente afin de préserver l’historique des changements
- Il devient aussi possible de stocker plusieurs présentations dans le même document
- En utilisant une version spéciale
pending au lieu d’un fichier de sauvegarde séparé, les changements non enregistrés peuvent être consignés fréquemment et silencieusement
- Comme on n’écrit que les différences plutôt que tout le document, l’opération écrit quelques Ko au lieu de plusieurs Mo
- Le temps d’enregistrement peut se compter en millisecondes plutôt qu’en secondes
- Même après un crash et un redémarrage, la majeure partie, voire la quasi-totalité, du travail de l’utilisateur peut être conservée
- Si l’utilisateur veut abandonner des changements non enregistrés, il suffit de revenir à une version précédente
Autres possibilités avec un format de fichier SQLite
- Un conteneur SQLite peut ajouter des fonctionnalités importantes à un format de fichier applicatif avec seulement trois tables
- On peut aussi exploiter schémas, index, triggers, vues et contraintes pour améliorer les performances, la commodité et la cohérence
- Quelques idées d’extensions
- Stocker une pile undo/redo automatique dans les tables de la base de données, afin de pouvoir revenir jusqu’aux sessions d’édition précédentes
- Ajouter une fonction de recherche en texte intégral à un jeu de diapositives, ou à plusieurs jeux de diapositives
- Décomposer
settings.xml en tables SQL pour le rendre plus facile à consulter et à modifier depuis d’autres applications
- Séparer les notes de présentateur de chaque diapositive dans une table dédiée, afin que les applications ou scripts tiers puissent y accéder facilement
- Au-delà d’un ordre linéaire simple des diapositives, prendre en charge des structures de présentation avec des chemins différents et des dérivations selon les réactions du public
Réticences fréquentes à propos de SQLite et réponses
- L’expérience des bases de données SQL d’entreprise peut susciter des réticences à utiliser SQLite comme format de fichier applicatif
- Beaucoup de bases de données d’entreprise recommandent de ne pas mettre de grandes chaînes ou des BLOB dans la base, mais de les stocker dans des fichiers séparés ; SQLite est différent
- N’importe quelle colonne SQLite peut stocker une chaîne ou un BLOB d’environ 1 Go
- Pour les chaînes et BLOB de 100 Ko ou moins, les performances d’I/O sont meilleures qu’avec des fichiers séparés
- L’idée que tous les schémas SQL devraient être en troisième forme normale (3NF) et ne stocker que de petits types primitifs peut aussi être limitante
- La théorie relationnelle est importante, mais dans un format de fichier réel, stocker des informations complexes comme du XML ou du JSON dans un champ texte peut être un choix acceptable
SQLite comme format de fichier applicatif
- Un fichier de base de données SQLite a presque la même taille qu’une archive ZIP contenant les mêmes informations, et peut parfois être plus petit
- Grâce aux mises à jour atomiques, il est possible d’écrire de petits changements dans le document en toute sécurité, ce qui réduit les I/O disque et améliore les performances de File/Save
- L’application peut réduire le temps de démarrage en ne lisant que le contenu nécessaire au premier écran
- Elle peut fortement réduire la consommation mémoire en ne gardant en mémoire que le contenu lié à l’affichage actuel et en laissant le reste sur disque
- Un schéma SQL peut représenter l’information plus directement et plus succinctement qu’une structure clé/valeur comme ZIP
- L’accessibilité pour les applications et scripts tiers s’en trouve améliorée
- Les fonctionnalités avancées comme la gestion de versions intégrée des documents et la récupération du travail après crash deviennent plus faciles à implémenter
- OpenDocument est un format déjà établi et bien conçu ; comme SQLite est apparu après OpenDocument, il ne s’agit pas de critiquer les choix existants
- Le document Application File Format propose d’autres idées pour utiliser SQLite comme format de fichier applicatif
1 commentaires
Avis sur Hacker News
Je développe une appli qui utilise SQLite comme format de fichier
Comme je veux conserver le flux classique où le fichier ne change que lorsque l’utilisateur modifie un document puis l’enregistre, je copie le contenu dans une base de données
:memory:à l’ouverture du fichier : https://www.sqlite.org/inmemorydb.htmlL’utilisateur peut manipuler librement les données, et l’appli reflète directement les changements dans le format de base de données, sans modèle de document séparé. Lors de l’enregistrement, je réécris le tout dans le fichier de base de données avec
VACUUM: https://www.sqlite.org/lang_vacuum.htmlCela fonctionne bien avec des fichiers de taille raisonnable, et dans mon appli ils restent toujours dans cette plage
Il vaudrait mieux enregistrer automatiquement directement dans la base de données et supprimer le bouton Enregistrer. C’est plus robuste face aux conflits, il n’y a qu’une seule base de données, donc moins de code et de bugs, et une écriture SQLite réussit ou échoue, sans état intermédiaire. À l’inverse, comme le cite l’article,
VACUUM INTOpeut produire une base de données de sortie incomplète ou corrompue en cas d’arrêt inattendu ou de coupure de courantSi vous utilisez SQLite comme il a été conçu, vous n’aurez pas à vous soucier de ce point pendant toute la durée de vie de SQLite
Il est nettement préférable d’enregistrer après chaque opération dans un emplacement temporaire, par exemple selon les répertoires XDG dans
~/.local/share/application/yourapp, puis de copier le fichier à l’endroit voulu quand l’utilisateur appuie sur Enregistrer. Même après une coupure de courant, en rouvrant l’appli on récupère presque le même état, avec au pire les dernières secondes perduesQuand l’utilisateur enregistre, on effectue un checkpoint pour fusionner le contenu du WAL dans la base de données principale
VACUUMcopie le contenu dans un fichier de base de données temporaire, puis écrase l’original, en utilisant lors de l’écrasement un journal de rollback ou le WAL comme une transaction normale. Il faut donc disposer d’environ jusqu’à deux fois la taille de l’original en espace libreVACUUM INTOécrit dans le fichier indiqué parINTOau lieu d’une base de données temporaire, et saute l’étape consistant à recopier ensuite par-dessus l’original. Il est important de savoir si l’on utilise réellementVACUUM, qui résiste aux coupures de courant, ouVACUUM INTO, qui semble vulnérable à une coupure pendant l’écriture et pourrait corrompre un fichier existant s’il porte déjà ce nomLe problème de SQLite, c’est que ce n’est pas un format de fichier standardisé
Il est bien documenté et largement compris, mais aucune norme ISO ne définit en détail la manière d’interpréter un fichier SQLite. Il en va de même pour les implémentations alternatives
Zip et XML ont une surface d’API beaucoup plus réduite que SQLite. L’API de SQLite ne se limite pas à quelques fonctions C : c’est le langage SQL lui-même, et implémenter sans corruption de données un parseur SQL, un optimiseur de requêtes, un compilateur, une machine virtuelle à bytecode, un moteur de recherche plein texte, etc., représente un travail bien plus important qu’un parseur XML
Pour une appli fermée spécialisée dans un domaine où l’interopérabilité ou la normalisation ISO ne comptent pas, SQLite est un bon format de fichier, mais je comprends qu’OpenOffice ait eu de vraies préoccupations de ce côté-là
La bibliothèque C SQLite est elle aussi dans le domaine public, son code source est entièrement ouvert, elle gère le format de fichier, et son niveau de documentation est supérieur à celui de la plupart des normes ISO. Il existe aussi des bindings pour presque tous les grands langages
Si le problème est qu’un éventuel format OpenDocument stocké dans un fichier SQLite reste encore à créer et à documenter, c’est autre chose. Les normes ISO sont utiles, mais si nous avions dû attendre que l’ISO définisse les formats de fichiers, il y aurait eu très peu de choses utilisables
C’est comme le fait qu’il n’est pas nécessaire d’implémenter toutes les fonctionnalités d’un tableur pour lire une feuille de calcul LibreOffice. Ce qu’il faut, c’est la capacité de reconstruire les tables ; ensuite, on peut parcourir les données avec du code impératif écrit dans le langage de son choix pour obtenir les informations voulues
Réimplémenter cette bibliothèque serait un gros chantier, mais c’est le même type de travail que réimplémenter du code utilisant le format de fichier OpenDocument. Le format de fichier lui-même est assez simple
Si la compatibilité vous inquiète, on peut faire en sorte que le document soit aussi accessible depuis une autre base de données, comme MySQL
Je pensais que l’adoption de SQLite par Audacity allait grandement améliorer la fonction d’enregistrement des fichiers, mais en pratique il y avait beaucoup de pièges.
Sous Linux, si l’on enregistre dans un nouveau fichier sur un montage NTFS appartenant à root mais accessible en écriture à tous, créé via
/etc/fstab, cela échouait pour des raisons du type erreur de permissions ; en revanche, enregistrer dans un fichier existant fonctionnait normalement.Dès qu’on modifie un projet, le fichier sur disque est modifié ; si l’on met un projet Audacity dans Git comme un gros blob binaire, cela crée des diffs Git inutiles. Même après enregistrement, des données obsolètes ou supprimées restent dans le fichier SQLite jusqu’à la fermeture de la fenêtre du projet ; si l’on ne ferme pas la fenêtre avant de committer, elles peuvent se retrouver dans le dépôt. Je me souviens qu’avant il fallait lancer manuellement
VACUUMsur le fichier.aup3, mais maintenant fermer la fenêtre suffit. Ça rappelle le Fast Save de Word 2003.Quand un projet qui aurait dû faire quelques centaines de Mo se retrouvait à plusieurs Go et qu’il fallait économiser de l’espace disque, Mix and Render était la solution pour les travaux simples à une seule piste. Cela ne changeait pas l’audio, mais permettait de nettoyer les résidus lors de l’enregistrement et de la fermeture.
Ce n’est clairement pas un problème propre à SQLite, mais un problème de couche applicative. Audacity 2 semblait avoir une notion d’espace de travail temporaire, tandis qu’Audacity 3 semble utiliser le fichier
.aup3lui-même comme espace de travail.J’ai regardé le format Audacity 3 : il stocke les données de projet correspondant aux anciens fichiers
.aupsous forme de XML dans une table à une seule ligne, mais au lieu de les écrire telles quelles en texte, il les encode avec un simple codeur à dictionnaire, ce qui m’a beaucoup surpris. Cela rend l’interopérabilité et l’inspection bien plus difficiles, nuit même très légèrement aux performances, et le gain d’espace doit représenter quelques Ko, une erreur d’arrondi face à des fichiers audio de plusieurs centaines de Mo.Du point de vue de Git, il vaut mieux utiliser un format texte facile à comparer et à fusionner. Je ne sais pas à quel point un dump SQLite est pratique de ce point de vue.
Si c’est important, on peut le réparer manuellement, mais en général il suffit de jeter le fichier pour que ça refonctionne.
Très bon article. Cela dit, j’aime le fait qu’OpenDocument soit un ensemble de fichiers XML dans une archive Zip.
On peut générer assez facilement des documents comme des feuilles de calcul, sans grosse bibliothèque connaissant le format du document.
Il arrive que les utilisateurs d’un service web veuillent exploiter, dans plusieurs outils, des données exportées sous forme de lignes de tableau. Le CSV UTF-8 est ouvert, conventionnel et utilisable, mais quiconque a déjà fourni du CSV à des utilisateurs finaux connaît la douleur des blocages dans les applications de tableur.
J’ai enregistré une feuille de calcul d’exemple en ODS d’OpenDocument et en XLSX, le monstre XML de Microsoft appelé OOXML, puis j’ai simplement compris les bases du format XML. J’ai réduit l’archive Zip aux éléments indispensables, marqué l’emplacement où le contenu devait être inséré, et généré un nouveau fichier de feuille de calcul à la demande. Je peux maintenant produire les mêmes données en CSV, ODS, XLSX et JSON.
Ce serait aussi possible avec SQLite, mais ce serait un peu plus complexe et le développement serait plus lent. Pouvoir créer un document modèle avec une suite bureautique, puis aller fouiller dans le XML du fichier enregistré, est une fonctionnalité de niche mais appréciable.
Excel avec une locale comme
nl_NLest particulièrement problématique, car il se comporte comme si le séparateur de colonnes des fichiers CSV était codé en dur comme étant le point-virgule. Microsoft a en effet décidé, tristement célèbrement, que les Néerlandais n’utilisent pas de virgule dans les fichiers de valeurs séparées par des virgules.localeconv()->decimal_point. Si la valeur est,, Excel utilise le point-virgule à la fois dans les fichiers CSV et dans le langage d’expression des formules.Avant, on pouvait le configurer à l’ouverture d’un CSV/TXT dans Excel, et c’est encore possible dans LibreOffice, mais avec la simplification générale de l’UI, l’option a été déplacée quelque part dans le menu ou l’onglet de ruban
Data. Il faut ouvrir un nouveau classeur et trouver la bonne option ; pour gagner du temps, mieux vaut utiliser LibreOffice.Cette partie m’a vraiment surpris. J’ai du mal à croire qu’une sous-requête ne soit pas nécessaire.
SELECT manifest, versionId, max(checkinTime) FROM version;On dit que, dans SQLite, cette deuxième requête utilisant
max(checkinTime)fonctionne effectivement bien et renvoie une réponse définie. Dans d’autres moteurs de base de données SQL, elle renverrait une réponse non définie ou une erreur, mais dans SQLite elle renvoie lemanifestet leversionIdde l’entrée ayant lecheckinTimemaximal.Dans ce cas, il n’y a pas besoin de sous-requête : il suffit de trier par
checkinTimepuis de limiter à une ligne :select manifest, versionId, checkinTime from version order by checkinTime desc limit 1Cela devrait fonctionner au moins avec SQLite et PostgreSQL. Dans Oracle, il fallait utiliser
where rownum=1, si je me souviens bien, ce qui nécessitait une sous-requête.GROUP BY manifest, versionId ORDER BY 3 DESC LIMIT 1, ou d’une CTE qui calcule lecheckinTimemaximal puis fait une jointure.Mais s’il y a plusieurs lignes avec le même
checkinTimemaximal, il y a de l’aléatoire et cela peut devenir un fusil pointé sur le pied, donc je n’utilise pas vraiment les fonctionnalités propres à SQLite3 de ce genre. Pour choisir la meilleure ligne de manière déterministe, il faut une méthode explicite semblable à celle ci-dessus.Dans Postgres, on peut faire quelque chose de similaire avec une requête
DISTINCT ON. C’est l’une des tâches qui m’ont semblé les plus difficiles en SQL alors qu’elle paraît simple.manifestetversionIdne sont pas fonctionnellement dépendants demax(checkinTime).Par exemple, il peut y avoir deux lignes ayant la même valeur de
checkinTime, et cette valeur peut être le maximum.J’ai déjà lancé un produit qui utilisait à la fois SQLite et des fichiers XML
L’une des améliorations consistait à déplacer vers des fichiers XML quelques tables contenant peu de données. Les fichiers étaient petits et rarement utilisés, ce qui simplifiait la couche d’accès aux données et le diagnostic ; ils étaient écrits en XML indenté par tabulations sur plusieurs lignes
Demander à un responsable technique chargé de diagnostiquer le produit d’ouvrir une base de données SQLite était assez contraignant. Mais pour les parties principales du produit, SQLite était de très loin supérieur aux fichiers XML. Les versions précédentes utilisaient des fichiers XML, qui posaient des problèmes de montée en charge faute d’une bonne méthode de mise à jour incrémentale
L’avantage du XML — son format lisible par l’humain — ne fonctionne vraiment que lorsque le fichier est petit et que le schéma est conçu pour un XML facile à lire. Le fait de devoir réécrire tout le fichier XML à chaque fois, ainsi que la complexité qui apparaît à mesure que les fonctionnalités se multiplient, rognent rapidement le principal atout du XML
Les cas où un utilisateur ordinaire doit manipuler directement l’intérieur d’un document bureautique sont suffisamment rares pour que l’apprentissage d’un lecteur SQLite constitue une barrière à l’entrée acceptable. Les limites de XML+Zip pour les écritures arbitraires au milieu d’un fichier ne peuvent pas être surmontées même par la loi de Moore
TEXTouBLOBde SQLite sont compressés, ou s’il suppose que l’appelant compresse le BLOB avant l’écritureODT a été conçu avec la standardisation en tête. Le format précédent était aussi très similaire, mais il s’appuyait fortement sur des standards existants comme XHTML, SVG et CSS
S’il n’était pas possible de référencer des standards existants, la spécification ODT elle-même deviendrait soudain énorme. L’effort nécessaire pour mettre à jour le standard semble aussi considérable, et il y a eu peu de progrès ces dernières années
En pratique, le format SQLite pourrait être proposé comme option, mais le navire des formats de documents bureautiques semble déjà avoir pris le large. Cela dit, c’est un bon argument en faveur de la formalisation de la spécification SQLite en standard officiel
À part quelques défauts — par exemple l’explosion des styles locaux et des plages de texte due à l’attribut
ooo:rsid, les feuilles de calcul non clairsemées, ou encore le mécanisme étrange de style des tableaux — c’est un balisage très bien conçu pour ce type de données documentaires. Il offre un bon équilibre entre le balisage sémantique et les présentations que les utilisateurs veulent réellement obtenirÀ l’inverse, Office OpenXML contient des balises vides de mise en forme avec état qui, dans DOCX, activent ou désactivent la mise en gras du texte qui suit
Associer un format de fichier à SQLite a quelque chose qui sent mauvais
SQLite est excellent, mais assez particulier dans ce domaine. Comme il fait beaucoup de choses, il est difficile à reproduire tel quel
Or, dans ce cas, a-t-on vraiment besoin d’autant de fonctionnalités ? Non. Il suffit d’une sémantique de transaction basique et sûre, et de la capacité à stocker une structure de tables simple ; pas besoin de tout le standard SQL ni même d’un optimiseur de requêtes
Il existe peut-être de meilleurs formats de fichier, mais ce serait mieux que ce soit un format distinct de SQLite
Sa taille est inférieure à 1 Mo et https://sqlite.org/footprint.html, avec toutes les fonctionnalités activées, il fait 750 Ko : https://www.sqlite.org/about.html
On peut retirer pas mal de fonctionnalités à la compilation, et il semble aussi exister des options pour ajuster ou réduire le planificateur de requêtes : https://www.sqlite.org/compile.html
En plus, il y a cette formule : « SQLite ne concurrence pas les bases de données client/serveur. SQLite concurrence
fopen()» : https://www.sqlite.org/whentouse.htmlAu bout du compte, ce dont on a besoin, ce n’est pas d’une base de données en soi, mais d’une bibliothèque qui fournisse une API et un comportement de base de données
SQLITE_BUSYétait assez pénibleJe sais que les échecs de sérialisation sont attendus dans le traitement des transactions, mais avec SQLite il était difficile de distinguer les échecs persistants, comme une sorte d’auto-interblocage, des problèmes temporaires de mises à jour concurrentes. Si l’échec est temporaire, il suffit de réexécuter la closure qui définit le travail de la transaction ; s’il est persistant, cela ne sert à rien
Une partie du problème vient du fait que
sqlite3_stmtcombine à la fois la nature d’une instruction préparée et celle d’un jeu de résultats. On a tendance à le conserver longtemps pour mettre en cache le bytecode compilé, mais si l’itération s’arrête au milieu, il peut encore détenir un verrou à ce moment-là. Cela peut provoquer des échecs inattendus de montée en niveau du verrouJ’ai fini par éliminer le problème en produisant des rapports d’erreur détaillés avec
sqlite3_next_stmt,sqlite3_stmt_busyetsqlite3_sql. Même pour un usage personnel, le code de reprise de transaction était rempli de journalisation optionnelle et de commentaires. La logique de reprise de transaction pour PostgreSQL était bien plus simpleUn autre point qui m’a surpris est la documentation indiquant qu’en mode WAL avec
synchronous=NORMAL, une transaction validée peut être annulée après une perte d’alimentation ou un crash système : https://sqlite.org/pragma.html#pragma_synchronousCe n’était pas pertinent pour mon application
Si Richard Hipp et son entreprise présentaient un standard SQLite ISO/IEC/ANSI/ETSI dont ils ne s’écarteraient jamais, une analyse juridique montrant l’absence de brevets ayant un impact, ainsi que plusieurs implémentations compatibles de SQLite conservant tous ses avantages, alors on pourrait discuter de sa recommandation comme format de fichier. Sinon, cela revient à mettre une forte dépendance sur une implémentation à source unique et à la faire peser aussi sur les utilisateurs
XML, ASN.1 et JFIF sont des standards officiels, et ZIP est également devenu un standard officiel, ISO/IEC 21320-1:2015, lors du processus de normalisation d’OpenDocument
Le plus important pour un document, c’est que tout le monde puisse le lire. Réduire le temps de mise à jour sur disque est secondaire. Il ne faut pas oublier ce que l’on a appris lorsque Microsoft a dévoyé les organismes de normalisation pour maintenir l’enfermement propriétaire : https://arstechnica.com/uncategorized/2008/10/norwegian-standards-body-implodes-over-ooxml-controversy/
Ce choix ne peut pas être si mauvais
Un autre exemple est celui des tuiles de cartes raster. Ce sont, en pratique, de petites images carrées qui peuvent se compter par millions
J’ai testé Zip, tar, le système de fichiers et SQLite : SQLite était le plus rapide et le plus compact, meilleur même que les archives ordinaires sans surcoût
SQLite a un gros inconvénient : les BLOB obtenus depuis la base de données ne peuvent pas être
mmapet doivent donc être copiés ailleurs. Un fichier Zip, s’il n’est pas compressé ou s’il est compressé avec un encodage particulier comme PVRTC, peut êtremmaptel quelOpenDocument, ce sont des images compressées et du XML. Au final, cela signifie qu’il faut analyser l’ensemble du format et le charger en mémoire
Je ne vois pas très bien en quoi SQLite améliorerait cela. XML n’est pas idéal, mais comme il est compressé en Zip, la pénalité de taille n’est pas très importante
Tous les avantages listés dans l’article sur SQLite peuvent être mis en œuvre si l’on utilise SQLite comme modèle d’exécution du document. C’est possible à la fois sur disque et en mémoire, mais SQLite n’a pas besoin d’être le format de transmission
Au contraire, SQLite pourrait être plus volumineux que le format actuel. Au fil des modifications, de l’espace inutilisé peut apparaître, avec de la fragmentation et des zones clairsemées. S’il faut optimiser à chaque fois, les avantages comme l’enregistrement rapide disparaissent aussi
Les formats qui ont besoin de mises à jour delta et de recherches rapides via index, sans devoir charger tout le fichier en mémoire, utilisent effectivement souvent SQLite comme format de fichier. Mais j’ai l’impression qu’OpenDocument était un mauvais exemple à choisir comme cible pour SQLite dans ce scénario hypothétique
Si l’on utilise SQLite comme format sur disque et que l’application est correctement implémentée, on peut éviter de se retrouver dans un état corrompu
XML/Zip peut aussi obtenir quelque chose de similaire avec une astuce de renommage, mais SQLite fournit cela dans un unique fichier sur disque. Si l’on utilise déjà SQLite comme modèle en mémoire, il n’y a pas vraiment de raison de ne pas l’utiliser aussi comme format disque/de transmission. À ce stade, c’est presque gratuit
Le problème de taille de fichier devrait pouvoir se traiter avec
VACUUM