- Présentation d’une méthode qui exploite les fonctions JSON de SQLite pour stocker le document JSON brut tel quel, puis extraire et indexer les champs nécessaires via des colonnes générées virtuelles (virtual generated columns)
- Avec la fonction
json_extract, les données internes au JSON peuvent être manipulées comme des colonnes, avec des requêtes exécutées à la vitesse des index B-tree - Chaque fois qu’un nouveau modèle de requête est nécessaire, il est possible d’étendre le système en ajoutant des colonnes et des index sans migration de données
- Cette approche permet d’obtenir à la fois la souplesse des données sans schéma et les performances d’une base de données relationnelle
- Elle est mise en avant comme un pattern pratique offrant une structure simple et de hautes performances pour les développeurs qui utilisent SQLite
Combiner SQLite et les fonctionnalités JSON
- SQLite prend en charge les fonctions et opérateurs JSON, ce qui permet de stocker et de manipuler directement des données JSON
- Le document JSON peut être conservé tel quel dans une colonne, et seules les informations nécessaires sont extraites sous forme de colonnes virtuelles
- Cette méthode permet de traiter les données avec souplesse, sans définition préalable de schéma
- L’équipe DB Pro a utilisé SQLite de manière intensive ces derniers mois et a validé ces capacités en conditions réelles
- Lorsqu’il est correctement configuré, SQLite peut être utilisé de façon fiable en production
Colonnes générées virtuelles (Generated Columns)
json_extractpermet de définir certaines valeurs du JSON comme colonnes générées virtuelles- Ces colonnes ne stockent pas réellement les données : elles sont calculées au moment de la requête et utilisables immédiatement
- Aucun backfill ni duplication de données n’est nécessaire
- Il est par exemple possible d’extraire un champ précis d’un document JSON pour le manipuler comme une colonne classique
Ajout d’index et gains de performance
- En ajoutant un index sur une colonne virtuelle, les données JSON peuvent elles aussi être recherchées à la vitesse des index B-tree, comme des colonnes ordinaires
- Une colonne virtuelle indexée offre les mêmes performances qu’une colonne d’une base de données relationnelle
- Cette approche permet d’effectuer des recherches rapides même lorsque les données JSON deviennent volumineuses
Ajouter de nouveaux modèles de requête
- Si, plus tard, il devient nécessaire de rechercher sur un nouveau champ, il suffit d’ajouter une nouvelle colonne virtuelle et un nouvel index
- Exemple : extraire le champ
user_idet créer un index dessus - Il n’est pas nécessaire de modifier les lignes existantes ni d’effectuer une migration
- Exemple : extraire le champ
- Cela permet d’obtenir une extensibilité immédiate des requêtes sans changer la structure des données
Avantages et portée du pattern
- Ce pattern combine la souplesse du stockage JSON sans schéma et les performances d’indexation d’une base relationnelle
- Il n’est pas nécessaire de décider de la stratégie d’indexation dès la phase de conception initiale
- Il est possible d’optimiser au moment voulu en ajoutant colonnes et index selon les besoins
- Il est présenté aux développeurs utilisant SQLite comme une méthode de traitement des données à la fois simple et puissante
- DB Pro annonce d’autres articles à venir sur diverses fonctionnalités de SQLite
2 commentaires
Commentaires sur Hacker News
Il est possible d’encoder directement des documents JSON sous forme de B-tree sérialisé
Cela permet d’interroger immédiatement les champs internes à la vitesse de l’index, et comme le document lui-même est déjà indexé, il n’y a plus besoin de parsing
Ce format s’appelle Lite³. C’est un projet sur lequel je travaille moi-même
Lien GitHub
J’apprécie particulièrement que Lite³ prenne en charge les données binaires avec
lite3_val_bytesJSONB encode la longueur des tableaux et les offsets ensemble, ce qui permet d’ajuster le compromis entre efficacité de compression et vitesse
Lite³ permet les mises à jour in-place, mais il faut exécuter périodiquement un “vacuum” pour éviter que des données sensibles ne restent présentes
JSONB est difficile à mettre à jour sans réencodage, alors que Lite³ peut être nettoyé assez simplement en parcourant la structure
La compressibilité semble probablement meilleure avec JSONB, mais je trouve que la conception de Lite³ est une approche très astucieuse
Je maintiens moi aussi un compilateur ASN.1, donc ce genre de format de sérialisation m’intéresse beaucoup. Lite³ m’a donné de nouvelles idées
J’aime beaucoup SQLite, mais pour l’analytique, j’utilise plus souvent DuckDB
DuckDB utilise un fichier unique comme SQLite, tout en traitant de très gros jeux de données extrêmement rapidement
Même sur un MacBook M2, il est très rapide avec 20 millions d’enregistrements
Par exemple, on peut lire directement des fichiers JSON avec la requête suivante
On peut aussi charger une colonne de type JSON et utiliser une syntaxe à la Postgres comme
col->>'$.key'Cela dit, la comparaison avec SQLite est un peu injuste. SQLite sert à construire des systèmes, tandis que DuckDB est destiné à l’analyse
DuckDB est un peu délicat à déployer sur plusieurs plateformes
Je pensais que l’usage de Generated Column pour les performances JSON était une méthode courante
Je l’ai déjà utilisé aussi dans Postgres pour conserver comme clé étrangère une clé située dans une colonne JSON. C’était une approche un peu “maudite”, mais elle fonctionnait bien
Par exemple Article de référence
Si le schéma JSON change, le parsing ou les migrations peuvent échouer
J’ai découvert cette technique récemment grâce à un exemple d’optimisation proposé par Claude Code
C’était un point que j’avais raté parce que je ne connaissais pas cette fonctionnalité récente de SQLite, et le gain de performances était assez important
La leçon, c’est que même avec des outils familiers, il est bon de relire périodiquement la documentation
J’ai écrit l’article de blog après avoir vu en 2023 sur HN un commentaire de bambax
Lien vers le commentaire d’origine
On peut créer un index sans projeter directement le JSON, mais une colonne calculée (computed column) simplifie les requêtes
Avant MS-SQL 2025(v17), la prise en charge de JSON était limitée, donc cette méthode était indispensable
J’ai ouvert l’article sur HN et j’ai trouvé amusant que mon commentaire y soit cité, et qu’en plus ce commentaire soit précisément le sujet du billet
Voir la phrase “Merci, bambax !” m’a fait sourire. SQLite est vraiment un super outil
Intéressant, mais on ne pourrait pas simplement utiliser "Index On Expression" de SQLite ?
Par exemple
CREATE INDEX idx_events_type ON events(json_extract(data, '$.type'))Cela dit, si la syntaxe du chemin JSON diffère ne serait-ce qu’un peu, l’index peut ne plus être utilisé. En revanche, une Virtual Generated Column garantit toujours l’index
Exemple : projet recordlite
Par exemple,
json_extract(data, "$.type")etdata -> '$.type'sont interprétés différemmentAutrement dit, si l’expression de la clause WHERE change, l’index devient sans effet
J’aimerais que les développeurs évitent de mettre toutes leurs données dans des colonnes JSON(B) alors qu’ils disposent d’un schéma cohérent
Cela complique la mise en place des index, la gestion des contraintes, et ajoute beaucoup d’overhead à l’usage réel
Par exemple, dans un environnement Haskell+TypeScript, il est bien plus pratique de sérialiser en JSON des structures imbriquées complexes
Exemple : stocker dans une seule table les résultats de plusieurs processeurs de paiement, ou gérer dans un site de petites annonces des attributs différents selon les articles
En C# ou en JS/TS aussi, la gestion devient plus simple si l’on utilise en complément des outils de validation de types (Zod, OpenAPI, etc.)
Au final, c’est une question d’équilibre entre maintenabilité et performances. L’idée centrale de cet article est qu’on peut aussi indexer facilement du JSON
Les performances en lecture peuvent être suffisamment compensées par les index
Par exemple, dans un système de tarification produit, représenter en JSON des règles de remise atypiques propres à certains clients offre bien plus de souplesse
Si l’on utilisait XML au lieu de JSON, ce serait le même modèle que les bases documentaires (document store) des années 90–2000
On parsait à l’insertion et à la mise à jour, puis on n’accédait plus qu’aux index lors des requêtes
Le fait que SQLite propose ce genre de fonctionnalité en standard est vraiment fascinant
À la fin du XXe siècle… il y avait ce qu’on appelait une universal database… (aujourd’hui c’est vrai, mais à l’époque c’était faux.)