SSD Nodes Learn 8GB de RAM — $66/an
Guides Matt ConnorPar Matt Connor · Mis à jour le 2026-08-02

DuckDB ou SQLite sur un serveur : lequel choisir ?

SQLite gère l’état transactionnel, DuckDB analyse Parquet et CSV. Découvrez pourquoi un VPS utilise souvent les deux, avec un exemple concret pour chacun.

DuckDB vs SQLite sur un serveur : la réponse en une phrase

SQLite est un moteur OLTP (traitement transactionnel en ligne) : il stocke les données sous forme de lignes et est conçu pour lire et écrire quelques lignes à la fois, de manière sûre et rapide. DuckDB est un moteur OLAP (traitement analytique en ligne) : il stocke les données sous forme de colonnes et est conçu pour analyser des millions de lignes et renvoyer un résultat agrégé. Les deux sont des bibliothèques embarquées, les deux ouvrent un simple fichier, et aucun des deux n’exécute un processus serveur à superviser.

La réponse honnête à la question « lequel choisir ? » est presque toujours « les deux, sur le même VPS ». Votre application conserve son état courant dans SQLite. Vos rapports lisent les fichiers Parquet et CSV avec DuckDB. Ils ne se concurrencent pas, car ils n’effectuent pas le même travail.

Pourquoi le stockage par lignes et le stockage par colonnes changent la réponse

SQLite écrit une ligne comme un bloc contigu dans une page. La récupération d’une commande par sa clé primaire touche une page d’index et une page de données, soit deux lectures. C’est exactement ce que fait une application des milliers de fois par seconde : lire cet utilisateur, mettre à jour cette session, insérer cette commande.

DuckDB écrit chaque colonne séparément et la compresse. Additionner amount_cents sur cinq millions de lignes ne lit que la colonne amount_cents, ignore tous les autres octets du fichier et exécute la somme avec du code vectorisé sur des lots de valeurs. Les autres colonnes ne sont jamais lues sur le disque. C’est de là que vient la rapidité.

Exécutez maintenant chaque moteur avec la charge de travail de l’autre. Pour additionner une colonne, SQLite doit parcourir chaque ligne et extraire la ligne complète de la page pour atteindre un champ. Il lit donc beaucoup plus de données sur le disque que nécessaire. Pour insérer une commande, DuckDB doit modifier le stockage de chaque colonne pour une seule valeur et prend un verrou d’écriture sur l’ensemble du fichier de base de données. Aucun des deux moteurs n’est défaillant. Chacun répond à une question pour laquelle il n’a pas été conçu.

Là où SQLite est efficace : l’état transactionnel de l’application

Choisissez SQLite lorsque les écritures sont petites, fréquentes et ne doivent pas être perdues. Sessions, commandes, lignes de file d’attente, paramètres, et tout ce qu’une requête web crée.

sudo apt update && sudo apt install -y sqlite3
sudo install -d -o "$USER" -g "$USER" /srv/app

Créez la table et activez le write-ahead logging dans la même étape.

sqlite3 /srv/app/app.db <<'SQL'
PRAGMA journal_mode = WAL;
CREATE TABLE IF NOT EXISTS orders (
  id           INTEGER PRIMARY KEY,
  customer     TEXT    NOT NULL,
  placed_at    TEXT    NOT NULL,
  amount_cents INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS orders_customer ON orders (customer);
INSERT INTO orders (customer, placed_at, amount_cents)
  VALUES ('ana', '2026-07-30T09:14:00Z', 4200);
SQL

La première ligne de la sortie est wal. Il s’agit de PRAGMA, qui indique le mode activé. C’est le réglage le plus utile sur un serveur. Dans le mode par défaut avec rollback journal, une écriture bloque tous les lecteurs. En mode WAL, les lecteurs continuent de lire le dernier état validé pendant qu’un writer ajoute des données. Un rapport lent ne bloque donc plus la requête web qui l’attend.

Vérifiez que la ligne a bien été renvoyée :

sqlite3 /srv/app/app.db "SELECT * FROM orders WHERE customer = 'ana';"

Vous obtenez 1|ana|2026-07-30T09:14:00Z|4200. Deux fichiers supplémentaires sont apparus à côté de la base de données : app.db-wal et app.db-shm. Ils lui appartiennent tous les deux. Copier uniquement app.db pendant que l’application s’exécute produit une sauvegarde incohérente. Ce point est traité plus loin.

SQLite autorise toujours un seul writer à la fois. Cette limite repose sur un lock, pas sur une file d’attente. Un second writer qui attend trop longtemps échoue donc avec database is locked au lieu de rester bloqué indéfiniment. Augmentez le délai d’attente avec PRAGMA busy_timeout = 5000; sur chaque connexion ouverte par votre application. Un délai de cinq secondes élimine la plupart de ces erreurs avec une charge web normale.

Quand DuckDB est performant : analyse de fichiers que vous avez déjà

Choisissez DuckDB lorsque la question commence par « combien », « quelle quantité » ou « quels sont les dix premiers », et que les données d’entrée sont constituées de nombreux fichiers CSV ou Parquet. Installez le client en ligne de commande, en version 1.5.5 en juillet 2026 :

curl https://install.duckdb.org | sh

Le script installe le binaire dans ~/.duckdb/cli/latest/duckdb et affiche la ligne qui l’ajoute à votre PATH. Vérifiez qu’il s’exécute :

~/.duckdb/cli/latest/duckdb :memory: "SELECT version();"

Créez un fichier réaliste à interroger. Cette commande écrit cinq millions de lignes de commandes au format Parquet, compressées avec zstd :

mkdir -p /srv/data
duckdb :memory: "
COPY (
  SELECT i                                                     AS id,
         'cust_' || (i % 5000)                                 AS customer,
         TIMESTAMP '2026-01-01 00:00:00' + INTERVAL (i) MINUTE AS placed_at,
         (i * 37) % 20000                                      AS amount_cents
  FROM range(5000000) t(i)
) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd);"

Posez maintenant la question analytique. Ouvrez le shell, activez le chronomètre et interrogez directement le fichier, sans étape d’importation :

.timer on
SELECT customer,
       count(*)                AS orders,
       sum(amount_cents)/100.0 AS revenue
FROM '/srv/data/orders.parquet'
GROUP BY customer
ORDER BY revenue DESC
LIMIT 5;

Lisez votre propre valeur dans .timer au lieu de faire confiance à une valeur publiée, car le résultat dépend de votre disque et du nombre de cœurs. C’est la forme du résultat qui compte. Il n’y a eu ni CREATE TABLE, ni INSERT, ni étape de chargement : DuckDB a lu le footer Parquet, déterminé les row groups nécessaires à la requête et lu uniquement ceux-ci. Un répertoire entier fonctionne de la même manière avec un glob, FROM '/srv/data/orders-*.parquet'. C’est ainsi qu’un mois d’exports quotidiens devient une seule requête.

La vitesse du disque constitue la limite inférieure de l’ensemble du traitement. Un scan de colonnes correspond à une longue lecture séquentielle. L’écart entre le stockage NVMe et le stockage SATA plus ancien sur un VPS est donc plus visible ici que lors des petites lectures aléatoires de SQLite.

Lire votre base de données SQLite depuis DuckDB

Les deux moteurs communiquent grâce à l’extension sqlite de DuckDB. Attachez la base de données de l’application en lecture seule afin qu’une requête d’analyse ne puisse jamais modifier l’état actif :

INSTALL sqlite;
LOAD sqlite;
ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY);
SELECT customer, count(*) AS orders
FROM app.orders
GROUP BY customer
ORDER BY orders DESC;

Cette commande lit les lignes dans le fichier SQLite au moment de la requête, sans effectuer de copie. Cette méthode est pratique, mais elle n’est pas rapide, car les données sur disque sont toujours stockées par lignes et DuckDB doit les parcourir. Utilisez-la pour l’export, pas pour un dashboard qui se recharge toutes les trente secondes :

COPY (SELECT * FROM app.orders)
  TO '/srv/data/orders-2026-07.parquet' (FORMAT parquet, COMPRESSION zstd);

Cette instruction constitue tout le modèle. SQLite conserve les lignes récentes et actives. Un export planifié convertit les périodes clôturées en fichiers Parquet. DuckDB répond à toutes les requêtes couvrant plusieurs mois, tandis que la base de données de l’application reste peu volumineuse, ce qui accélère ses écritures.

Exécutez l’export selon une planification plutôt que manuellement. Un ensemble composé d’un service et d’un timer systemd convient parfaitement : une unité qui exécute COPY et un timer qui la déclenche chaque nuit.

Exécuter les deux sur un seul VPS

Rien ici ne nécessite de conteneur ni de port. Les deux moteurs sont des bibliothèques. L’installation consiste donc en un paquet et un chemin de fichier. Si le reste de votre stack s’exécute déjà avec Docker Compose sur le même VPS, montez le répertoire de données dans le conteneur qui en a besoin au lieu d’ajouter un service de base de données, car aucun service n’est nécessaire.

Deux règles permettent d’éviter les problèmes avec cette configuration.

Attribuez un répertoire à chaque moteur : /srv/app pour le fichier SQLite écrit par l’application, /srv/data pour les fichiers Parquet lus par l’outil d’analytics. S’ils partagent un répertoire, une tâche de sauvegarde qui crée un snapshot de l’un entre en concurrence avec l’autre.

Ne faites pas pointer deux processus vers un même fichier de base de données DuckDB en mode lecture-écriture. Un seul processus peut ouvrir un fichier DuckDB en écriture. Le second échoue complètement à l’ouvrir. Plusieurs lecteurs sont possibles si chacun définit access_mode = 'READ_ONLY'. Ce comportement surprend les personnes habituées à SQLite, où plusieurs processus partagent régulièrement un fichier. Si votre outil d’analytics lit uniquement des fichiers Parquet, la question ne se pose jamais. C’est une raison supplémentaire de conserver l’état durable dans SQLite.

Les sauvegardes diffèrent, et la différence pose problème

Une base de données SQLite en cours d'exécution se compose de trois fichiers. Si vous les copiez avec cp pendant une écriture, vous obtenez un fichier qui s'ouvre, mais dont le contenu est incorrect. Utilisez la commande de sauvegarde du moteur. Elle crée un instantané cohérent pendant que l'application continue d'écrire :

sqlite3 /srv/app/app.db ".backup '/srv/backup/app-$(date -u +%Y%m%dT%H%M%SZ).db'"
sqlite3 /srv/backup/app-20260730T091400Z.db "PRAGMA integrity_check;"

integrity_check affiche ok pour une copie correcte. Toute autre valeur signifie que vous devez supprimer cet instantané et en créer un autre.

Les fichiers Parquet ne changent plus après leur écriture. Ils ne nécessitent donc aucun traitement particulier : sauvegardez le répertoire. Envoyez les deux chemins hors du serveur avec sauvegardes restic depuis votre VPS. Toute la couche de données tient ainsi dans deux répertoires, traités par une seule tâche de sauvegarde.

Modes d’échec et chaînes exactes affichées

Error: database is locked de SQLite signifie qu’une autre connexion a conservé le verrou d’écriture plus longtemps que le délai d’attente autorisé. Il ne s’agit pas d’une corruption. Définissez PRAGMA busy_timeout sur chaque connexion, puis recherchez une transaction longue qui aurait dû être divisée en plusieurs transactions courtes.

Error: unable to open database file après une modification des permissions signifie généralement que le processus peut écrire dans le fichier, mais pas dans son répertoire. SQLite crée app.db-wal et app.db-shm à côté de la base de données. Le répertoire lui-même doit donc être accessible en écriture, et pas uniquement le fichier .db.

IO Error: Could not set lock on file de DuckDB signifie qu’un second processus a déjà ouvert cette base de données en écriture. Fermez l’autre shell ou ouvrez le vôtre en lecture seule.

Out of Memory Error de DuckDB sur un petit VPS signifie qu’une requête nécessitait plus de mémoire de travail qu’elle n’en disposait. DuckDB utilise le disque pour les données temporaires lorsque c’est possible. Donnez-lui un emplacement où écrire ces données en ouvrant un fichier de base de données sur le disque plutôt que :memory:, et limitez sa consommation avec SET memory_limit = '2GB';. Sur une machine qui exécute d’autres services, cette limite empêche une requête ad hoc de faire sortir votre application de la RAM.

Binder Error: Referenced column "amount" not found lors de l’interrogation d’un fichier Parquet signifie presque toujours que le schéma du fichier n’est pas celui dont vous vous souvenez. Exécutez DESCRIBE SELECT * FROM '/srv/data/orders.parquet'; et consultez les vrais noms de colonnes renvoyés.

Comment choisir en pratique

Demandez quel est le schéma d’écriture. De nombreuses petites écritures qui doivent survivre à une coupure de courant indiquent qu’il faut utiliser SQLite. Demandez quel est le schéma de lecture. Des parcours complets avec des agrégations sur un historique long indiquent qu’il faut utiliser DuckDB. La plupart des systèmes réels répondent oui aux deux questions. La bonne approche consiste donc à attribuer à chaque moteur la tâche pour laquelle il est adapté, au lieu de demander à l’un de remplacer l’autre.

Évitez de déplacer l’état actif de l’application dans DuckDB parce qu’un rapport était lent. Le rapport était lent à cause de l’organisation du stockage. La solution consiste donc à effectuer un export, et non à réécrire votre chemin d’écriture.

FAQ

DuckDB peut-il remplacer SQLite pour la base de données de mon application ?

Pas pour une base qui reçoit souvent des écritures. DuckDB verrouille l’ensemble du fichier de base de données en écriture, autorise un seul processus en lecture-écriture à la fois et est optimisé pour les modifications en masse, pas pour les insertions ligne par ligne. Conservez l’état transactionnel dans SQLite et laissez DuckDB le lire avec ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY); lorsqu’un rapport en a besoin.

DuckDB est-il vraiment plus rapide que SQLite pour l’analytique ?

Pour les parcours et les agrégations sur une grande table, oui. La raison tient à l’organisation du stockage, pas à une astuce d’optimisation. DuckDB ne lit que les colonnes demandées par la requête et traite les valeurs par lots, tandis que SQLite doit parcourir des lignes entières pour atteindre un champ. Pour récupérer une seule ligne par clé primaire, le résultat s’inverse : SQLite accède à deux pages, tandis que DuckDB accède au stockage de chaque colonne.

Ai-je besoin de beaucoup de RAM pour exécuter DuckDB sur un VPS ?

Non, mais attribuez-lui une limite et un espace disque. Ouvrez un fichier de base de données plutôt que :memory: afin que DuckDB puisse déporter les résultats intermédiaires sur le disque, puis définissez SET memory_limit = '2GB'; sur une valeur que votre VPS peut fournir. Sans limite, un GROUP BY volumineux peut augmenter Out of Memory Error ou évincer d’autres services de la RAM.

Comment importer mes données SQLite dans Parquet ?

Attachez le fichier SQLite depuis DuckDB et exportez directement le résultat d’une requête avec COPY (SELECT * FROM app.orders) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd);. Exécutez cette opération selon une planification pour les périodes clôturées, par exemple les lignes du mois dernier, et laissez les lignes récentes dans SQLite, où l’application continue de les modifier.

Quelle base dois-je sauvegarder, et comment ?

Les deux, de manière différente. Créez des instantanés SQLite avec sqlite3 app.db ".backup '/srv/backup/app.db'" plutôt qu’avec cp, car une base de données en cours d’exécution est également un -wal et un fichier -shm, et une copie simple peut être incohérente. Les fichiers Parquet ne changent plus une fois écrits ; il suffit donc de copier le répertoire.

#duckdb#sqlite#database#analytics#parquet#auto-hébergement