Files
Oxicloud/doc/POSTGRESQL-BEST-PRACTICES.md
T

250 lines
6.5 KiB
Markdown
Raw Normal View History

2025-04-09 00:21:20 +02:00
# 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"](https://wiki.postgresql.org/wiki/Don%27t_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:
```sql
-- 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:
```sql
-- 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:
```sql
-- 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:
```sql
-- 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:
```sql
-- Í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:
```sql
-- 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:
```sql
-- 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:
```sql
-- 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:
```rust
// 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:
```rust
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:
```rust
// 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
- [Wiki PostgreSQL - Don't Do This](https://wiki.postgresql.org/wiki/Don%27t_Do_This)
- [Documentación oficial de PostgreSQL](https://www.postgresql.org/docs/)
- [Guía de migraciones de OxiCloud](DATABASE-MIGRATIONS.md)