improve postgresql performance
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user