Aller au contenu

Sauvegarde et restauration

Stream Fusion propose un système complet de sauvegarde automatisée pour les trois bases de données critiques : DuckDB (matching IMDB), Meilisearch (cache public full-text), et PostgreSQL (cache privé + configuration).


Concept

Trois types de dumps coexistent, avec des périmètres de partage distincts :

  • DuckDB

    Base IMDB locale. Dump transférable entre pairs.
    Méthode : CHECKPOINT + copie fichier imdb.duckdb

  • Meilisearch

    Index full-text public. Dump transférable entre pairs.
    Méthode : API create_dump() Meilisearch

  • PostgreSQL

    Cache privé + configuration. Local uniquement (contient secrets).
    Méthode : pg_dump --format=custom

PostgreSQL : jamais de partage entre pairs

Les dumps PostgreSQL contiennent des données sensibles (clés API utilisateur, tokens debrid, secrets de configuration). Ils ne sont jamais exposés via l'API peer et restent strictement locaux.

DuckDB et Meilisearch : partage entre pairs

Les dumps DuckDB (base IMDB) et Meilisearch (index full-text) sont conçus pour être partagés entre instances appairées. Une nouvelle instance peut ainsi récupérer une base IMDB pré-construite (gain de ~8h de build) et un index Meilisearch déjà peuplé.


Architecture

graph TB
    subgraph Scheduler["Taskiq Scheduler"]
        CRON_DUCK["db_dump_create<br/>DuckDB: Dimanche 4h"]
        CRON_MEILI["db_dump_create<br/>Meilisearch: Dimanche 4h"]
        CRON_PG["db_dump_create<br/>PostgreSQL: Quotidien 3h"]
    end

    subgraph Worker["Taskiq Worker"]
        TASK_CREATE["db_dump_create()"]
        TASK_PULL["db_peer_pull()"]
        TASK_RESTORE["db_dump_restore()"]
    end

    subgraph Storage["Stockage"]
        DUCK_DIR["/data/backups/duckdb/"]
        MEILI_DIR["/data/backups/meilisearch/"]
        PG_DIR["/data/backups/postgresql/"]
    end

    subgraph Redis["Redis DB5"]
        LOCK["taskiq:lock:db_dump_create:*"]
        TOKEN["Download tokens<br/>TTL 600s"]
    end

    subgraph Admin["Panneau d'administration"]
        UI["/admin/maintenance/"]
    end

    CRON_DUCK --> TASK_CREATE
    CRON_MEILI --> TASK_CREATE
    CRON_PG --> TASK_CREATE
    UI -->|"Déclenchement manuel"| TASK_CREATE
    UI -->|"Pull depuis un pair"| TASK_PULL
    UI -->|"Restauration"| TASK_RESTORE
    TASK_CREATE --> Storage
    TASK_CREATE --> LOCK
    TASK_PULL --> TOKEN
    TASK_PULL --> Storage

Le système utilise :

Composant Rôle
Tâches Taskiq db_dump_create (création), db_peer_pull (pull distant), db_dump_restore (restauration)
Verrous Redis taskiq:lock:db_dump_create:{type} (2h TTL) — empêche les exécutions concurrentes
Redis DB5 Tokens de téléchargement (TTL configurable via DB_DUMP_TOKEN_TTL)
SyncProgressTracker Suivi de progression en temps réel (affiché dans l'UI admin)
Panneau d'administration Section maintenance à /admin/maintenance/

Dump DuckDB

Méthode correcte : CHECKPOINT + copie fichier

Le dump DuckDB est réalisé par copie directe du fichier imdb.duckdb après un CHECKPOINT qui vide le WAL (Write-Ahead Log) sur disque :

CHECKPOINT  →  copie imdb.duckdb  →  tar.gz  →  SHA-256

Cette méthode est fiable, rapide, et bit-perfect. Le fichier copié est identique à la base en cours d'utilisation.

Ne JAMAIS utiliser EXPORT DATABASE / IMPORT DATABASE

La méthode EXPORT DATABASE de DuckDB exporte les tables au format CSV, et IMPORT DATABASE les réimporte depuis CSV. Ce round-trip CSV est cassé sur les données IMDB réelles :

  • Les titres contiennent des apostrophes (L'Affaire, Today's Special), des guillemets et des caractères spéciaux
  • Les données originales entrent dans DuckDB via executemany() Python, pas via parsing CSV
  • Le parsing CSV de DuckDB est plus strict et échoue ou corrompt ces valeurs
  • Résultat : perte de données silencieuse, matching IMDB dégradé

La copie directe du fichier est également plus rapide et ne nécessite aucune reconstruction d'index.

Contenu du dump

Fichier dans l'archive Description
imdb.duckdb Base DuckDB complète (tables imdb_basics, imdb_akas, imdb_ratings, imdb_tmdb)

Le WAL n'est pas inclus dans l'archive car le CHECKPOINT l'a vidé avant la copie.


Dump Meilisearch

Méthode : API create_dump()

Le dump Meilisearch utilise l'API native de Meilisearch :

create_dump()  →  wait_for_task()  →  récupération .dump  →  tar.gz  →  SHA-256
  1. Appel à l'API POST /dumps de Meilisearch
  2. Attente de la complétion de la tâche (timeout 5 minutes)
  3. Récupération du fichier .dump le plus récent dans meili_db_path/dumps/
  4. Archivage en tar.gz avec SHA-256

Restauration

Restauration Meilisearch : redémarrage requis

Contrairement à DuckDB et PostgreSQL, la restauration d'un dump Meilisearch nécessite un redémarrage du conteneur Meilisearch avec l'option --import-dump :

meilisearch --import-dump /meili_data/dumps/meili_dump.dump

L'interface d'administration affiche un avertissement explicite avant toute restauration.


Dump PostgreSQL

Méthode : pg_dump --format=custom

Le dump PostgreSQL utilise l'outil standard pg_dump au format custom (compressé) :

pg_dump --format=custom --no-owner --no-acl --dbname <database>
Caractéristique Détail
Format Custom (compressé, restaurable par pg_restore)
Rétention 7 jours (configurable via DB_DUMP_PG_RETENTION)
Partage Jamais — local uniquement
Restauration pg_restore --clean --if-exists

Restauration destructive

La restauration PostgreSQL utilise pg_restore --clean --if-exists et détruit toutes les données actuelles. La confirmation est demandée dans l'interface d'administration.


Configuration

Variables d'environnement

Variable Défaut Description
DB_DUMP_SCHEDULE_ENABLED False Active la création automatique des dumps DuckDB + Meilisearch
DB_DUMP_CRON 0 4 * * 0 Planification des dumps DuckDB + Meilisearch (Dimanche 4h)
DB_DUMP_RETENTION 3 Nombre de dumps conservés par type (DuckDB, Meilisearch)
DB_DUMP_TOKEN_TTL 600 TTL des tokens de téléchargement en secondes (Redis DB5)
DB_DUMP_PG_SCHEDULE_ENABLED False Active la création automatique des dumps PostgreSQL
DB_DUMP_PG_CRON 0 3 * * * Planification des dumps PostgreSQL (Quotidien 3h)
DB_DUMP_PG_RETENTION 7 Nombre de dumps PostgreSQL conservés
BACKUPS_DB_DIR /data/backups Répertoire racine des sauvegardes (volume partagé)
MEILI_DB_PATH /meili_data Répertoire des données Meilisearch

Planifications par défaut

  • DuckDB + Meilisearch

    Dimanche à 4h du matin (0 4 * * 0)

    Rétention : 3 dumps

    Interrupteur principal : DB_DUMP_SCHEDULE_ENABLED

  • PostgreSQL

    Tous les jours à 3h du matin (0 3 * * *)

    Rétention : 7 dumps

    Interrupteur principal : DB_DUMP_PG_SCHEDULE_ENABLED

Changement de planification

Les expressions cron nécessitent un redémarrage du worker pour être prises en compte. Les flags d'activation (*_SCHEDULE_ENABLED) sont vérifiés à chaque exécution et peuvent être modifiés à chaud.


Panneau d'administration

La gestion des dumps est accessible dans le panneau d'administration, section Maintenance (/admin/maintenance/).

Fonctionnalités disponibles

Action Description
Créer un dump Déclenchement manuel d'un dump pour DuckDB, Meilisearch ou PostgreSQL
Pull depuis un pair Téléchargement d'un dump DuckDB ou Meilisearch depuis une instance appairée
Restaurer Restauration d'un dump local (avec confirmation + avertissement destructif)
Supprimer Suppression d'un dump obsolète
Planification Activer/désactiver et configurer le cron de création automatique

Suivi de progression

L'interface affiche une badge de progression en temps réel pendant les opérations longues :

  • Création : progression du dump (DuckDB/Meilisearch/PostgreSQL)
  • Pull distant : pourcentage de téléchargement (downloading 45%)
  • Restauration : progression de l'import (DuckDB/PostgreSQL) ou staging (Meilisearch)

Transfert entre pairs

Flux de pull

sequenceDiagram
    participant Admin as Administrateur
    participant Local as Instance locale
    participant Remote as Instance distante (pair)

    Admin->>Local: Déclenche pull (db_type, peer_id, dump_id)
    Local->>Remote: POST /api/peer/db/{type}/dump/download<br/>(signé HMAC + corps chiffré)
    Remote-->>Local: Token de download (payload chiffré Fernet)
    Local->>Remote: GET /api/peer/db/{type}/dump/download/{token}
    Remote-->>Local: Stream du fichier tar.gz (1 MB chunks)
    Local->>Local: Vérification SHA-256
    alt db_type = duckdb
        Local->>Local: Extraction → copie imdb.duckdb → reset cache
    else db_type = meilisearch
        Local->>Local: Extraction vers meili_data/dumps/
        Local->>Admin: ACTION REQUISE: redémarrer Meilisearch avec --import-dump
    end

Sécurité

  • Les requêtes sont signées HMAC-SHA256 et le corps est chiffré Fernet (AES-128)
  • Les tokens de téléchargement ont une durée de vie limitée (TTL configurable, défaut 600 secondes)
  • Les tokens sont stockés dans Redis DB5 avec expiration automatique
  • La vérification SHA-256 garantit l'intégrité du fichier téléchargé

Restriction

Les dumps PostgreSQL ne sont jamais exposés via l'API peer. Seuls DuckDB et Meilisearch sont transférables.


Restauration manuelle

En cas de problème nécessitant une intervention directe :

# 1. Arrêter l'application
docker compose stop app

# 2. Extraire le dump
tar xzf /data/backups/duckdb/2025-01-01_04-00.tar.gz -C /tmp/

# 3. Remplacer le fichier
cp /tmp/imdb.duckdb /data/imdb_db/imdb.duckdb

# 4. Redémarrer
docker compose start app
# Restaurer un dump custom
pg_restore --clean --if-exists --no-owner --no-acl \
  --dbname stream_fusion \
  /data/backups/postgresql/2025-01-01_03-00.dump
# 1. Extraire le dump
tar xzf /data/backups/meilisearch/2025-01-01_04-00.tar.gz \
  -C /meili_data/dumps/

# 2. Redémarrer Meilisearch avec import
meilisearch --import-dump /meili_data/dumps/meili_dump.dump

Bonnes pratiques

Recommandations

  • Activez les dumps automatiquesDB_DUMP_SCHEDULE_ENABLED=True et DB_DUMP_PG_SCHEDULE_ENABLED=True en production
  • Sauvegardez /data/backups — montez ce volume sur un stockage externe ou inclus dans vos backups système
  • Testez vos restaurations — un dump non testé est un dump qui n'existe pas
  • DuckDB est le plus critique — une corruption = ~8h de reconstruction complète. Les dumps DuckDB doivent être votre priorité de sauvegarde
  • Ne partagez jamais les dumps PostgreSQL — ils contiennent toutes les données sensibles de votre instance