QoreDB LogoQoreDB
Retour au blog
ArchitectureJournal technique

Le crash recovery dans QoreDB : restaurer l'état de session après un plantage

Un client de base de données desktop accumule du contexte au fil d'une session. Onglets ouverts sur des tables, requêtes en cours d'écriture, vues de schéma, drafts SQL non sauvegardés. Quand l'application redémarre, soit par crash, soit par mise à jour, soit par fermeture…

Raphaël – Creator of QoreDBRaphaël – Creator of QoreDB
5 min de lecture
Le crash recovery dans QoreDB : restaurer l'état de session après un plantage

Un client de base de données desktop accumule du contexte au fil d'une session. Onglets ouverts sur des tables, requêtes en cours d'écriture, vues de schéma, drafts SQL non sauvegardés. Quand l'application redémarre, soit par crash, soit par mise à jour, soit par fermeture volontaire, on ne veut pas tout reconstruire à la main.

Le crash recovery de QoreDB répond à ce besoin avec une approche minimaliste : sérialiser en continu un instantané de l'état utile, le stocker localement, et le proposer au prochain démarrage si la connexion correspondante existe encore. Pas de service en arrière-plan, pas de base auxiliaire, pas de synchronisation distante.

Ce qui constitue l'état d'une session

Le snapshot que QoreDB persiste contient seulement ce qui ne peut pas être reconstruit à partir du backend : l'identifiant du workspace courant, l'identifiant de la connexion active, la liste des onglets ouverts avec leur type et leur titre, l'onglet actif, les drafts de requêtes par onglet, et l'état d'affichage des navigateurs de tables et de bases (vue data, structure ou info).

Volontairement, on ne sauvegarde pas les résultats de requêtes. Un résultat est volumineux, potentiellement sensible, et il a sa propre durée de validité côté serveur. Le rejouer au redémarrage est plus honnête que le restituer depuis un cache local périmé. Les onglets de type plugin-output sont également exclus du snapshot : ils dépendent du cycle de vie d'un plugin et il est plus propre de les laisser disparaître que de tenter une réhydratation partielle.

Une sauvegarde débouncée à 600 millisecondes

Le frontend déclenche une sauvegarde à chaque changement de l'état observable : ouverture d'un onglet, frappe dans l'éditeur SQL, changement d'onglet actif, modification d'une vue de browser. Sans précaution, cela ferait écrire localStorage à chaque keystroke. Le SessionProvider applique donc un debounce de 600 ms : on annule la sauvegarde précédente si une nouvelle arrive avant l'échéance.

Cette fenêtre est un compromis assumé. Trop courte, on écrit pour rien et on dégrade la frappe sur les machines lentes. Trop longue, on perd les dernières secondes de travail en cas de crash. 600 ms est suffisamment court pour qu'un utilisateur perçoive la perte comme négligeable, et suffisamment long pour absorber le rythme normal de la frappe.

Pourquoi localStorage plutôt qu'un fichier côté Rust

QoreDB tourne dans une WebView Tauri. Il aurait été possible de pousser l'état vers le backend Rust à chaque snapshot, qui l'aurait écrit dans un fichier dédié. Cela n'apporte rien ici. L'état de session est de pure UI : il n'a de sens que dans la fenêtre, il est lu seulement par le frontend, et il ne survit pas à un changement de machine. Le faire transiter par un appel Tauri ajouterait un aller-retour à chaque débounce sans bénéfice.

localStorage offre une API synchrone, persistante entre redémarrages de la WebView, et locale à l'utilisateur OS. La clé utilisée est qoredb_crash_recovery. Les anciennes clés qoredb_crash_recovery_v1 et qoredb_crash_recovery_v2 sont lues si la clé actuelle est absente, puis migrées vers le nouveau format avant d'être supprimées. La migration est silencieuse, sans intervention utilisateur.

Restaurer une session au démarrage

Au montage du SessionProvider, le hook useRecovery tente de lire le snapshot. Trois cas se présentent. Si la connexion référencée n'existe plus dans le vault du workspace, l'utilisateur en est informé et peut écarter le snapshot. Si le snapshot a été créé dans un autre workspace que celui actuellement ouvert, un message dédié l'indique : c'est un cas légitime quand on a plusieurs workspaces et qu'un crash s'est produit dans un autre. Si la connexion existe, une bannière propose la restauration.

La restauration appelle la commande Tauri connectSavedConnection, récupère un nouveau sessionId côté Rust, puis recompose les onglets depuis le snapshot. Les drafts SQL sont réinjectés dans l'éditeur via initialQuery, l'onglet actif est restauré, et le snapshot est immédiatement effacé une fois la restitution réussie. Si la reconnexion échoue, l'état reste en localStorage : un nouvel essai sera proposé au prochain démarrage.

L'utilisateur peut aussi écarter le snapshot manuellement. C'est volontaire : on ne décide pas à sa place de relancer une connexion qui pourrait pointer vers une base de production. Le geste de cliquer pour restaurer est la confirmation explicite.

TTL et paramètres de confidentialité

Un snapshot a une durée de vie. Par défaut, tout snapshot plus vieux que 24 heures est considéré comme caduc et supprimé à la lecture. La valeur est paramétrable dans les réglages via le champ ttlHours. Cette borne évite qu'un crash oublié il y a une semaine ne propose la restitution d'un état qui n'a plus de sens.

Un second réglage, saveQueryDrafts, contrôle la sauvegarde des contenus de l'éditeur SQL. Quand il est désactivé, la structure des onglets est toujours persistée, mais le texte des requêtes en cours ne l'est pas. C'est utile dans deux contextes : machines partagées, et requêtes qui contiennent occasionnellement des littéraux sensibles. Le réglage est local, écrit dans une clé localStorage séparée, et ne nécessite aucun arbitrage côté backend.

Ce que ce design implique en pratique

Le crash recovery de QoreDB est entièrement local à la machine et à l'utilisateur OS. Aucun état n'est envoyé ni stocké en dehors de la WebView de l'application. C'est cohérent avec la promesse local-first de l'outil : on récupère sa session après un plantage sans qu'aucun service distant n'ait à être impliqué.

Le mécanisme est conçu pour un usage desktop mono-utilisateur. Le snapshot porte un projectId et un connectionId qui ne valent que pour l'installation courante. Cette absence de portabilité est volontaire : un état de session restauré sur une autre machine n'aurait pas accès aux connexions chiffrées du vault local et n'aurait donc aucun moyen de se reconnecter.

Une fiabilité gagnée par la simplicité

Un crash recovery utile n'a pas besoin d'être sophistiqué. Il a besoin d'écrire fréquemment, de lire de façon robuste, de ne jamais bloquer l'application au démarrage, et de demander confirmation avant de relancer une connexion. Ces quatre propriétés tiennent dans deux fichiers TypeScript de quelques centaines de lignes au total, plus une intégration dans le SessionProvider. Pas de base SQLite locale, pas de service en arrière-plan, pas de schéma versionné.

Le résultat est un mécanisme que l'on comprend en lisant le code, qui se teste comme un module pur, et qui reste fiable parce qu'il a très peu de surface où échouer. C'est exactement le type de composant qu'on veut sur un chemin critique : invisible quand tout va bien, présent quand on en a besoin.

Newsletter

Restez informé des nouveautés

Rejoignez notre newsletter pour recevoir les mises à jour majeures, les nouveaux drivers et nos coulisses techniques.

🎁 Bonus : Recevez notre fiche mémo d'optimisation SQL — 9 pages, PostgreSQL / MySQL / SQLite (PDF) !
Partager