Saltar a contenido

Arquitectura de la base de datos

KanjiIQ utiliza PostgreSQL 15 como su almacén de datos principal, desplegado dentro del clúster de Kubernetes con almacenamiento persistente.

Descripción general del esquema

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
    }

Almacenamiento de contenido multilingüe

KanjiIQ almacena las traducciones usando columnas JSONB de PostgreSQL en lugar de tablas de traducción separadas. Esto proporciona un almacenamiento multilingüe flexible y sin esquema fijo:

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

¿Por qué JSONB?

  • No se necesitan migraciones de esquema al añadir nuevos idiomas
  • Una sola consulta recupera todas las traducciones de un kanji
  • Los operadores JSONB de PostgreSQL permiten búsquedas eficientes por idioma
  • Indexación GIN disponible para búsqueda de texto completo en las traducciones

Almacenamiento en Kubernetes

PostgreSQL se ejecuta como un Deployment de Kubernetes con 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 datos no está expuesta fuera del clúster — solo el pod del backend puede acceder a ella a través del Service jlpt-postgres en el puerto 5432.

Decisiones de diseño clave

Claves primarias UUID

Todas las tablas usan claves primarias UUID en lugar de enteros auto-incrementales. Esto permite:

  • Generación distribuida de IDs (sin secuencia central)
  • Exposición segura de IDs en las APIs (no adivinables)
  • Futura replicación multi-región sin conflictos de IDs

Eliminaciones en cascada

Las claves foráneas usan ON DELETE CASCADE para mantener la integridad referencial. Eliminar un usuario automáticamente elimina sus sesiones de estudio, resultados de cuestionarios y resultados de exámenes.

Preferencias de usuario como JSONB

Las preferencias y estadísticas de usuario se almacenan como JSONB en lugar de columnas fijas:

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

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

Esto evita migraciones de esquema cada vez que se añade una nueva preferencia o estadística a la aplicación.

Estrategia de respaldo

La base de datos sigue la regla de respaldo 3-2-1:

  • 3 copias de los datos (en vivo + 2 respaldos)
  • 2 medios diferentes (PVC + almacenamiento de objetos)
  • 1 copia fuera del sitio (Hetzner Object Storage)

Las exportaciones diarias con pg_dump se almacenan con retención de 30 días. Consulta la sección de despliegue para detalles operativos.