Zum Inhalt

Datenbankarchitektur

KanjiIQ verwendet PostgreSQL 15 als primären Datenspeicher, bereitgestellt innerhalb des Kubernetes-Clusters mit persistentem Speicher.

Schemaübersicht

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
    }

Mehrsprachige Inhaltsspeicherung

KanjiIQ speichert Übersetzungen mit PostgreSQL-JSONB-Spalten anstelle separater Übersetzungstabellen. Dies bietet flexible, schemalose mehrsprachige Speicherung:

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

Warum JSONB?

  • Keine Schemamigration erforderlich beim Hinzufügen neuer Sprachen
  • Eine einzige Abfrage ruft alle Übersetzungen für ein Kanji ab
  • PostgreSQL JSONB-Operatoren ermöglichen effiziente sprachspezifische Abfragen
  • GIN-Indizierung verfügbar für Volltextsuche über Übersetzungen hinweg

Kubernetes-Speicher

PostgreSQL läuft als Kubernetes Deployment mit einem PersistentVolumeClaim:

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

Die Datenbank ist nicht außerhalb des Clusters erreichbar — nur der Backend-Pod kann sie über den jlpt-postgres-Service auf Port 5432 erreichen.

Wichtige Designentscheidungen

UUID-Primärschlüssel

Alle Tabellen verwenden UUID-Primärschlüssel anstelle von automatisch inkrementierenden Ganzzahlen. Dies unterstützt:

  • Verteilte ID-Generierung (keine zentrale Sequenz)
  • Sichere ID-Offenlegung in APIs (nicht erratbar)
  • Zukünftige Multi-Region-Replikation ohne ID-Konflikte

Kaskadenlöschungen

Fremdschlüssel verwenden ON DELETE CASCADE, um die referenzielle Integrität zu wahren. Das Löschen eines Benutzers entfernt automatisch dessen Lernsitzungen, Quiz-Ergebnisse und Testergebnisse.

Benutzereinstellungen als JSONB

Benutzereinstellungen und Statistiken werden als JSONB statt als feste Spalten gespeichert:

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

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

Dies vermeidet Schemamigrationen für jede neue Einstellung oder Statistik, die der Anwendung hinzugefügt wird.

Backup-Strategie

Die Datenbank folgt der 3-2-1-Backup-Regel:

  • 3 Kopien der Daten (Live + 2 Backups)
  • 2 verschiedene Medien (PVC + Objektspeicher)
  • 1 externe Kopie (Hetzner Object Storage)

Tägliche pg_dump-Exporte werden mit 30-tägiger Aufbewahrung gespeichert. Siehe den Deployment-Abschnitt für betriebliche Details.