Files
Oxicloud/doc/POSTGRESQL-BEST-PRACTICES.md
T
2025-04-09 00:21:20 +02:00

6.5 KiB

Mejores Prácticas para PostgreSQL en OxiCloud

Este documento describe las mejores prácticas para el uso de PostgreSQL en OxiCloud, siguiendo recomendaciones oficiales y la guía "Don't Do This" de PostgreSQL.

Diseño de Esquema

Tipos de Datos

Uso de TEXT en lugar de VARCHAR(n)

OxiCloud utiliza el tipo TEXT en lugar de VARCHAR(n) con límites arbitrarios para campos de texto:

-- Recomendado ✅
username TEXT NOT NULL UNIQUE

-- Evitar ❌
username VARCHAR(32) NOT NULL UNIQUE

Razones:

  • TEXT y VARCHAR tienen el mismo rendimiento y ocupan el mismo espacio.
  • VARCHAR(n) impone un límite arbitrario que puede causar errores inesperados.
  • PostgreSQL optimiza internamente ambos tipos de manera idéntica.

Uso de TIMESTAMPTZ para Fechas y Horas

OxiCloud utiliza TIMESTAMP WITH TIME ZONE (o TIMESTAMPTZ) para todos los campos de fecha/hora:

-- Recomendado ✅
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP

-- Evitar ❌
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP

Razones:

  • TIMESTAMPTZ almacena un punto en el tiempo unívoco.
  • Gestiona correctamente las zonas horarias y cambios de horario de verano.
  • Evita problemas de ambigüedad al trabajar con diferentes husos horarios.

Evitar CHAR(n)

OxiCloud no utiliza el tipo CHAR(n) en ningún caso:

-- Recomendado ✅
country_code TEXT NOT NULL CHECK (length(country_code) = 2)

-- Evitar ❌
country_code CHAR(2) NOT NULL

Razones:

  • CHAR(n) rellena con espacios hasta la longitud declarada.
  • Este comportamiento puede causar problemas sutiles en comparaciones.
  • Para valores de longitud fija, es mejor usar TEXT con una restricción CHECK.

Usar SERIAL con Precaución

OxiCloud utiliza SERIAL solo en casos específicos, prefiriendo IDENTITY cuando es posible:

-- Recomendado para PostgreSQL 10+ ✅
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY

-- Alternativa aceptable para compatibilidad ✅
id SERIAL PRIMARY KEY

Razones:

  • SERIAL tiene comportamientos extraños con gestión de dependencias y permisos.
  • Las columnas IDENTITY (PostgreSQL 10+) ofrecen mejor integración con el sistema.

Índices y Restricciones

Nombrado Consistente de Índices

OxiCloud sigue una convención de nomenclatura para índices:

-- Índice en una columna
CREATE INDEX IF NOT EXISTS idx_table_column ON schema.table(column);

-- Índice en múltiples columnas
CREATE INDEX IF NOT EXISTS idx_table_col1_col2 ON schema.table(col1, col2);

Uso de Restricciones Explícitas

OxiCloud define restricciones explícitas en lugar de depender de convenciones implícitas:

-- Restricción de unicidad
UNIQUE(user_id, item_id, item_type)

-- Restricción de comprobación
CHECK (storage_quota_bytes >= 0)

Consultas SQL

Evitar NOT IN con Subconsultas

OxiCloud evita el uso de NOT IN con subconsultas:

-- Recomendado ✅
SELECT * FROM files
WHERE NOT EXISTS (SELECT 1 FROM deleted_files WHERE deleted_files.id = files.id);

-- Evitar ❌
SELECT * FROM files
WHERE id NOT IN (SELECT id FROM deleted_files);

Razones:

  • NOT IN se comporta de manera inesperada con valores NULL.
  • NOT EXISTS es más eficiente y predecible.

Usar BETWEEN con Precaución

OxiCloud evita BETWEEN para rangos de fechas, prefiriendo comparaciones explícitas:

-- Recomendado ✅
WHERE timestamp_col >= '2025-01-01' AND timestamp_col < '2025-01-02'

-- Evitar ❌
WHERE timestamp_col BETWEEN '2025-01-01' AND '2025-01-02'

Razones:

  • BETWEEN incluye ambos extremos, lo que puede ser problemático para rangos de tiempo.
  • Usar >= y < es más claro para expresar rangos de tiempo.

Transacciones

Uso Explícito de Transacciones

OxiCloud implementa transacciones explícitas para operaciones que deben ser atómicas:

// Ejemplo de transacción explícita
let mut tx = pool.begin().await?;

// Operaciones dentro de la transacción
sqlx::query("INSERT INTO users (id, username) VALUES ($1, $2)")
    .bind(id)
    .bind(username)
    .execute(&mut *tx)
    .await?;

sqlx::query("INSERT INTO profiles (user_id, display_name) VALUES ($1, $2)")
    .bind(id)
    .bind(display_name)
    .execute(&mut *tx)
    .await?;

// Confirmar la transacción
tx.commit().await?;

Manejo de Errores en Transacciones

Las transacciones incluyen manejo adecuado de errores con rollback automático:

let result = sqlx::Transaction::begin(&pool).await.and_then(|mut tx| async move {
    // Operaciones dentro de la transacción
    let result1 = operation1(&mut tx).await?;
    let result2 = operation2(&mut tx).await?;
    
    // Confirmar la transacción si todo fue exitoso
    tx.commit().await?;
    
    Ok((result1, result2))
}).await;

// Si ocurre un error, la transacción se revierte automáticamente
if let Err(e) = &result {
    log::error!("Error en la transacción: {}", e);
}

Migraciones y Gestión de Esquema

Separación del Esquema del Código

OxiCloud separa la definición del esquema del código de la aplicación:

OxiCloud/
├── migrations/            # Archivos SQL de migración
├── src/
    ├── bin/migrate.rs     # Herramienta de migración
    ├── common/db.rs       # Solo conecta a la BD, no crea esquema

Uso de Migraciones Versionadas

Las migraciones siguen un formato versionado y se aplican secuencialmente:

20250408000000_initial_schema.sql
20250408000001_default_users.sql

Seguridad

Uso de Consultas Parametrizadas

OxiCloud utiliza consultas parametrizadas para todas las operaciones SQL:

// Recomendado ✅
sqlx::query("SELECT * FROM users WHERE username = $1")
    .bind(username)
    .fetch_one(&pool)
    .await?;

// Evitar ❌
sqlx::query(&format!("SELECT * FROM users WHERE username = '{}'", username))
    .fetch_one(&pool)
    .await?;

Razones:

  • Previene ataques de inyección SQL.
  • Permite la reutilización de planes de consulta.
  • Mejora el rendimiento general.

Configuración de Autenticación Segura

OxiCloud evita el uso de autenticación trust para conexiones TCP/IP:

# pg_hba.conf recomendado ✅
hostssl all all 0.0.0.0/0 scram-sha-256

# Evitar ❌
host all all 0.0.0.0/0 trust

Recursos Adicionales