Aller au contenu

Architecture de la base de données

KanjiIQ utilise PostgreSQL 15 comme stockage de données principal, déployé au sein du cluster Kubernetes avec un stockage persistant.

Vue d'ensemble du schéma

erDiagram
    users ||--o{ study_sessions : has
    users ||--o{ quiz_results : has
    users ||--o{ test_results : has
    kanji ||--o{ quiz_results : referenced_in
    vocabulary ||--o{ quiz_results : referenced_in

    users {
        uuid id PK
        text email
        text password_hash
        jsonb preferences
        jsonb stats
        timestamp created_at
    }

    kanji {
        uuid id PK
        text character
        int jlpt_level
        jsonb meanings
        jsonb readings
        text example_sentences
    }

    vocabulary {
        uuid id PK
        text expression
        text reading
        int jlpt_level
        jsonb meanings
        text part_of_speech
    }

    locale_configs {
        uuid id PK
        text locale_code
        text[] default_languages
        text[] available_languages
    }

    regional_analytics {
        uuid id PK
        text country_code
        text request_path
        text user_agent
        text device_type
        boolean is_suspicious
        int response_status
        timestamp created_at
    }

    ip_blocklist {
        uuid id PK
        text ip_address
        text reason
        text blocked_by
        timestamp expires_at
        boolean is_active
    }

Stockage de contenu multilingue

KanjiIQ stocke les traductions en utilisant les colonnes JSONB de PostgreSQL plutôt que des tables de traduction séparées. Cela fournit un stockage multilingue flexible et sans schéma :

// kanji.meanings column
{
  "en": "mountain",
  "es": "montaña",
  "fr": "montagne",
  "ja": "やま",
  "pt": "montanha",
  "ar": "جبل",
  "zh-CN": "山"
}

Pourquoi JSONB ?

  • Aucune migration de schéma nécessaire lors de l'ajout de nouvelles langues
  • Une seule requête récupère toutes les traductions d'un kanji
  • Les opérateurs JSONB de PostgreSQL permettent des recherches efficaces par langue
  • L'indexation GIN est disponible pour la recherche plein texte dans les traductions

Stockage Kubernetes

PostgreSQL s'exécute en tant que Deployment Kubernetes avec un PersistentVolumeClaim :

# k8s/02-postgres-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
  namespace: jlpt-kanji
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

La base de données n'est pas exposée en dehors du cluster — seul le pod backend peut y accéder via le Service jlpt-postgres sur le port 5432.

Décisions de conception clés

Clés primaires UUID

Toutes les tables utilisent des clés primaires UUID au lieu d'entiers auto-incrémentés. Cela prend en charge :

  • La génération d'identifiants distribués (pas de séquence centrale)
  • L'exposition sûre des identifiants dans les API (non devinables)
  • La réplication multi-région future sans conflits d'identifiants

Suppressions en cascade

Les clés étrangères utilisent ON DELETE CASCADE pour maintenir l'intégrité référentielle. La suppression d'un utilisateur entraîne automatiquement la suppression de ses sessions d'étude, résultats de quiz et résultats de tests.

Préférences utilisateur en JSONB

Les préférences et statistiques des utilisateurs sont stockées en JSONB plutôt que dans des colonnes fixes :

// users.preferences
{
  "defaultLanguages": ["en", "pt", "es"],
  "studyLevels": ["N5", "N4"],
  "showAllLanguages": false
}

// users.stats
{
  "totalKanjiStudied": 245,
  "averageScore": 78.5,
  "streakDays": 12
}

Cela évite les migrations de schéma pour chaque nouvelle préférence ou statistique ajoutée à l'application.

Stratégie de sauvegarde

La base de données suit la règle de sauvegarde 3-2-1 :

  • 3 copies des données (en direct + 2 sauvegardes)
  • 2 supports différents (PVC + stockage objet)
  • 1 copie hors site (Hetzner Object Storage)

Des exports pg_dump quotidiens sont stockés avec une rétention de 30 jours. Voir la section Déploiement pour les détails opérationnels.