improve postgresql performance
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
# Sistema de Migraciones de Base de Datos
|
||||
|
||||
Este documento describe el sistema de migraciones de base de datos implementado en OxiCloud para gestionar cambios de esquema de forma controlada y segura.
|
||||
|
||||
## Descripción General
|
||||
|
||||
OxiCloud utiliza un sistema de migraciones basado en archivos SQL versionados para garantizar que los cambios en la estructura de la base de datos sean:
|
||||
|
||||
- Versionados y rastreables
|
||||
- Aplicados de forma consistente en todos los entornos
|
||||
- Reproducibles y comprobables
|
||||
- Independientes del código de la aplicación
|
||||
|
||||
## Estructura de Directorios
|
||||
|
||||
```
|
||||
OxiCloud/
|
||||
├── migrations/ # Directorio principal de migraciones
|
||||
│ ├── 20250408000000_initial_schema.sql # Migración 1: Esquema inicial
|
||||
│ ├── 20250408000001_default_users.sql # Migración 2: Usuarios por defecto
|
||||
│ └── ... # Futuras migraciones
|
||||
├── src/
|
||||
├── bin/
|
||||
│ └── migrate.rs # Herramienta CLI para ejecutar migraciones
|
||||
```
|
||||
|
||||
## Convenciones de Nomenclatura
|
||||
|
||||
Las migraciones siguen el formato: `YYYYMMDDHHMMSS_descripción_breve.sql`, donde:
|
||||
|
||||
- `YYYYMMDDHHMMSS`: Timestamp que garantiza el orden correcto (año, mes, día, hora, minuto, segundo)
|
||||
- `descripción_breve`: Descripción concisa del propósito de la migración
|
||||
- `.sql`: Extensión de archivo SQL
|
||||
|
||||
## Ejecución de Migraciones
|
||||
|
||||
Las migraciones se ejecutan mediante una herramienta CLI dedicada:
|
||||
|
||||
```bash
|
||||
cargo run --bin migrate --features migrations
|
||||
```
|
||||
|
||||
Este comando:
|
||||
1. Conecta con la base de datos configurada en el entorno
|
||||
2. Busca migraciones en el directorio `/migrations/`
|
||||
3. Compara las migraciones aplicadas con las disponibles
|
||||
4. Ejecuta secuencialmente las migraciones pendientes
|
||||
5. Registra las migraciones aplicadas en una tabla de control
|
||||
|
||||
## Creación de Nuevas Migraciones
|
||||
|
||||
Para crear una nueva migración:
|
||||
|
||||
1. Crea un nuevo archivo en el directorio `migrations/` siguiendo la convención de nomenclatura
|
||||
2. Define los cambios SQL en el archivo
|
||||
3. Asegúrate de que los cambios sean compatibles con la versión actual del esquema
|
||||
4. Ejecuta las migraciones con el comando correspondiente
|
||||
|
||||
Ejemplo de estructura para una nueva migración:
|
||||
|
||||
```sql
|
||||
-- Migración: Añadir tabla de etiquetas
|
||||
-- Descripción: Crea la tabla para almacenar etiquetas de archivos y sus relaciones
|
||||
|
||||
-- Crear tabla de etiquetas
|
||||
CREATE TABLE IF NOT EXISTS auth.tags (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
color TEXT NOT NULL DEFAULT '#3498db',
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id, name)
|
||||
);
|
||||
|
||||
-- Crear índices
|
||||
CREATE INDEX IF NOT EXISTS idx_tags_user_id ON auth.tags(user_id);
|
||||
|
||||
-- Tabla de relación entre archivos y etiquetas
|
||||
CREATE TABLE IF NOT EXISTS auth.file_tags (
|
||||
id SERIAL PRIMARY KEY,
|
||||
tag_id INTEGER NOT NULL REFERENCES auth.tags(id) ON DELETE CASCADE,
|
||||
file_id TEXT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(tag_id, file_id)
|
||||
);
|
||||
|
||||
-- Comentarios de documentación
|
||||
COMMENT ON TABLE auth.tags IS 'Almacena etiquetas definidas por usuarios';
|
||||
COMMENT ON TABLE auth.file_tags IS 'Relación muchos-a-muchos entre archivos y etiquetas';
|
||||
```
|
||||
|
||||
## Guía de Buenas Prácticas
|
||||
|
||||
1. **Migraciones Incrementales**: Cada migración debe representar un cambio atómico y coherente.
|
||||
|
||||
2. **Migraciones Idempotentes**: Cuando sea posible, usa comandos que pueden ejecutarse múltiples veces sin errores (ej. `CREATE TABLE IF NOT EXISTS`).
|
||||
|
||||
3. **Migraciones Forward-Only**: Diseña las migraciones para avanzar, no para revertir. Si necesitas deshacer un cambio, crea una nueva migración.
|
||||
|
||||
4. **Compatibilidad Hacia Adelante**: Las migraciones deben ser compatibles con el código existente y el que se va a desplegar.
|
||||
|
||||
5. **Prueba Antes de Desplegar**: Prueba las migraciones en un entorno similar al de producción antes de aplicarlas.
|
||||
|
||||
6. **Documentación**: Documenta el propósito y los cambios clave de cada migración con comentarios dentro del archivo SQL.
|
||||
|
||||
## Solución de Problemas
|
||||
|
||||
### Verificación del Estado de las Migraciones
|
||||
|
||||
Para verificar qué migraciones se han aplicado, OxiCloud incluye detección en tiempo de inicio:
|
||||
|
||||
```rust
|
||||
// Desde src/common/db.rs
|
||||
let migration_check = sqlx::query("SELECT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'auth' AND tablename = 'users')")
|
||||
.fetch_one(&pool)
|
||||
.await;
|
||||
|
||||
match migration_check {
|
||||
Ok(row) => {
|
||||
let tables_exist: bool = row.get(0);
|
||||
if !tables_exist {
|
||||
tracing::warn!("Las tablas de la base de datos no existen. Por favor, ejecuta las migraciones con: cargo run --bin migrate --features migrations");
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
tracing::warn!("No se pudo verificar el estado de las migraciones. Por favor, ejecuta las migraciones con: cargo run --bin migrate --features migrations");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Problemas Comunes
|
||||
|
||||
1. **Error de conexión a la base de datos**: Verifica la URL de conexión en la variable de entorno `DATABASE_URL`.
|
||||
|
||||
2. **Conflictos de migración**: Si una migración falla, revisa los mensajes de error para identificar conflictos con el esquema existente.
|
||||
|
||||
3. **Permisos insuficientes**: Asegúrate de que el usuario de la base de datos tenga permisos suficientes para crear esquemas, tablas e índices.
|
||||
|
||||
## Beneficios del Enfoque Basado en Migraciones
|
||||
|
||||
- **Separación de Responsabilidades**: Las migraciones están separadas del código de la aplicación.
|
||||
- **Automatización**: Facilita la automatización de despliegues y CI/CD.
|
||||
- **Historial de Cambios**: Proporciona un historial claro de cómo ha evolucionado el esquema.
|
||||
- **Colaboración**: Permite que múltiples desarrolladores contribuyan cambios al esquema de forma ordenada.
|
||||
- **Entornos Múltiples**: Garantiza que todos los entornos (desarrollo, pruebas, producción) tengan estructuras de base de datos idénticas.
|
||||
@@ -0,0 +1,171 @@
|
||||
# Base de Datos y Transacciones en OxiCloud
|
||||
|
||||
## Introducción a Transacciones Explícitas en la Base de Datos
|
||||
|
||||
Este documento describe la implementación de transacciones explícitas en OxiCloud para garantizar la integridad de los datos en operaciones de base de datos PostgreSQL.
|
||||
|
||||
## ¿Qué son las Transacciones?
|
||||
|
||||
Una transacción es una secuencia de operaciones de base de datos tratadas como una única unidad lógica. Las transacciones siguen las propiedades ACID:
|
||||
|
||||
- **Atomicidad**: Una transacción es "todo o nada". Si cualquier parte falla, toda la transacción falla.
|
||||
- **Consistencia**: La base de datos pasa de un estado válido a otro estado válido.
|
||||
- **Aislamiento**: Las transacciones simultáneas se comportan como si fueran secuenciales.
|
||||
- **Durabilidad**: Una vez confirmada, la transacción permanece confirmada incluso en caso de fallo del sistema.
|
||||
|
||||
## Implementación en OxiCloud
|
||||
|
||||
OxiCloud ahora utiliza un enfoque consistente para las transacciones de base de datos mediante la función `with_transaction`, que:
|
||||
|
||||
1. Comienza una transacción
|
||||
2. Ejecuta operaciones
|
||||
3. Confirma automáticamente si todo fue exitoso
|
||||
4. Revierte (rollback) automáticamente en caso de error
|
||||
|
||||
### Utilidad de Transacciones
|
||||
|
||||
En `src/infrastructure/repositories/pg/transaction_utils.rs` hemos implementado:
|
||||
|
||||
```rust
|
||||
/// Helper function to execute database operations in a transaction
|
||||
pub async fn with_transaction<F, T, E>(
|
||||
pool: &Arc<PgPool>,
|
||||
operation_name: &str,
|
||||
operation: F,
|
||||
) -> Result<T, E>
|
||||
where
|
||||
F: for<'c> FnOnce(&'c mut Transaction<'_, Postgres>) -> futures::future::BoxFuture<'c, Result<T, E>>,
|
||||
E: From<SqlxError> + std::fmt::Display
|
||||
{ ... }
|
||||
```
|
||||
|
||||
Esta función:
|
||||
- Recibe un pool de conexiones y un closure con operaciones
|
||||
- Maneja begin/commit/rollback automáticamente
|
||||
- Proporciona logging detallado del ciclo de vida de la transacción
|
||||
|
||||
### Ejemplo de Uso en Repositorios
|
||||
|
||||
```rust
|
||||
// Creación de un usuario con transacción explícita
|
||||
async fn create_user(&self, user: User) -> UserRepositoryResult<User> {
|
||||
with_transaction(
|
||||
&self.pool,
|
||||
"create_user",
|
||||
|tx| {
|
||||
Box::pin(async move {
|
||||
// Operación principal - insertar usuario
|
||||
sqlx::query("INSERT INTO auth.users ...")
|
||||
.bind(...)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
// Operaciones adicionales dentro de la misma transacción
|
||||
// ...
|
||||
|
||||
Ok(user_clone)
|
||||
})
|
||||
}
|
||||
).await
|
||||
}
|
||||
```
|
||||
|
||||
## Casos de Uso Implementados
|
||||
|
||||
### En UserPgRepository
|
||||
|
||||
1. **Creación de Usuario**
|
||||
- Garantiza que todas las operaciones de inserción son atómicas
|
||||
- Permite agregar operaciones relacionadas (como configuración de permisos)
|
||||
|
||||
2. **Actualización de Usuario**
|
||||
- Asegura que las modificaciones se apliquen completamente o no se apliquen en absoluto
|
||||
- Soporta operaciones combinadas como actualización de información de perfil y preferencias
|
||||
|
||||
### En SessionPgRepository
|
||||
|
||||
1. **Creación de Sesión**
|
||||
- Inserta la sesión y actualiza el timestamp de último acceso del usuario en una única transacción
|
||||
- Garantiza consistencia entre sesiones y datos de usuario
|
||||
|
||||
2. **Revocación de Sesiones**
|
||||
- Asegura que la revocación de una sesión o de todas las sesiones de un usuario sea atómica
|
||||
- Permite registrar eventos de seguridad dentro de la misma transacción
|
||||
|
||||
## Niveles de Aislamiento
|
||||
|
||||
OxiCloud admite diferentes niveles de aislamiento de transacciones mediante `with_transaction_isolation`:
|
||||
|
||||
```rust
|
||||
// Ejemplo de uso con nivel de aislamiento específico
|
||||
with_transaction_isolation(
|
||||
&pool,
|
||||
"operacion_critica",
|
||||
sqlx::postgres::PgIsolationLevel::Serializable,
|
||||
|tx| { ... }
|
||||
).await
|
||||
```
|
||||
|
||||
Los niveles de aislamiento disponibles son:
|
||||
|
||||
1. **Read Committed** (predeterminado)
|
||||
- Garantiza que los datos leídos están confirmados
|
||||
- No previene lecturas no repetibles o fantasma
|
||||
|
||||
2. **Repeatable Read**
|
||||
- Garantiza que las lecturas sean consistentes durante toda la transacción
|
||||
- Previene lecturas no repetibles pero no lecturas fantasma
|
||||
|
||||
3. **Serializable**
|
||||
- Nivel más alto de aislamiento
|
||||
- Garantiza que las transacciones se comporten como si se ejecutaran en serie
|
||||
- Puede causar errores de serialización que requieren reintento
|
||||
|
||||
## Mejores Prácticas
|
||||
|
||||
1. **Duración de Transacciones**
|
||||
- Mantén las transacciones lo más cortas posible
|
||||
- Evita operaciones de larga duración dentro de transacciones
|
||||
|
||||
2. **Manejo de Errores**
|
||||
- Los errores dentro de una transacción provocan rollback automático
|
||||
- Utiliza logging adecuado para diagnosticar fallos
|
||||
|
||||
3. **Límites de Transacción**
|
||||
- Define claramente dónde comienzan y terminan las transacciones
|
||||
- Agrupa operaciones relacionadas en una sola transacción
|
||||
|
||||
4. **Aislamiento Apropiado**
|
||||
- Usa el nivel de aislamiento más bajo adecuado para tu caso de uso
|
||||
- Considera serializable para operaciones críticas con posibilidad de conflicto
|
||||
|
||||
## Ventajas de Transacciones Explícitas
|
||||
|
||||
1. **Integridad de Datos Mejorada**
|
||||
- Garantía ACID para operaciones complejas
|
||||
- Prevención de estados inconsistentes
|
||||
|
||||
2. **Mejor Manejo de Errores**
|
||||
- Rollback automático ante fallos
|
||||
- Comportamiento predecible en caso de error
|
||||
|
||||
3. **Concurrencia Segura**
|
||||
- Manejo adecuado de operaciones simultáneas
|
||||
- Prevención de condiciones de carrera
|
||||
|
||||
4. **Rendimiento**
|
||||
- Reducción de trips a la base de datos
|
||||
- Operaciones en lote para mejor eficiencia
|
||||
|
||||
## Consideraciones de Rendimiento
|
||||
|
||||
- Las transacciones añaden cierta sobrecarga
|
||||
- El rendimiento puede verse afectado por:
|
||||
- Duración de la transacción
|
||||
- Nivel de aislamiento
|
||||
- Número de registros afectados
|
||||
- Contención por bloqueos
|
||||
|
||||
## Conclusión
|
||||
|
||||
La implementación de transacciones explícitas en OxiCloud mejora significativamente la robustez del sistema y garantiza la integridad de los datos en escenarios complejos. El enfoque modular y la API de transacciones simplificada permiten extender fácilmente estos beneficios a nuevas funcionalidades.
|
||||
@@ -0,0 +1,168 @@
|
||||
# File System Safety in OxiCloud
|
||||
|
||||
This document describes the implementation of file system safety mechanisms in OxiCloud to ensure data integrity and durability during file operations.
|
||||
|
||||
## Introduction
|
||||
|
||||
Data integrity is critical in a file storage system like OxiCloud. When files are written to disk, it's important to ensure that:
|
||||
|
||||
1. Writes are atomic - they either complete fully or not at all
|
||||
2. Data is properly synchronized to persistent storage
|
||||
3. Directory entries are properly updated and persisted
|
||||
4. The system can recover from unexpected crashes or power failures
|
||||
|
||||
OxiCloud implements several mechanisms to achieve these goals.
|
||||
|
||||
## The Problem: Buffered I/O and Data Loss
|
||||
|
||||
Standard file system operations in many programming languages and operating systems use buffered I/O by default:
|
||||
|
||||
```rust
|
||||
// This operation may not immediately persist to disk
|
||||
fs::write(path, content)
|
||||
```
|
||||
|
||||
When an application writes data, the operating system typically:
|
||||
|
||||
1. Accepts the write into memory buffers
|
||||
2. Acknowledges completion to the application
|
||||
3. Schedules the actual disk write for later
|
||||
|
||||
This creates a window where a system crash or power failure can result in data loss, as the data may exist only in memory buffers that haven't been flushed to disk.
|
||||
|
||||
## OxiCloud's Solution
|
||||
|
||||
OxiCloud implements a comprehensive approach to file system safety through the `FileSystemUtils` service, which provides:
|
||||
|
||||
### 1. Atomic Write Pattern
|
||||
|
||||
Files are written using a safe atomic pattern:
|
||||
|
||||
```rust
|
||||
/// Writes data to a file with fsync to ensure durability
|
||||
/// Uses a safe atomic write pattern: write to temp file, fsync, rename
|
||||
pub async fn atomic_write<P: AsRef<Path>>(path: P, contents: &[u8]) -> Result<(), IoError>
|
||||
```
|
||||
|
||||
This implements a write-then-rename pattern:
|
||||
1. Write to a temporary file in the same directory
|
||||
2. Call `fsync` to ensure data is on disk
|
||||
3. Atomically rename the temp file to the target file
|
||||
4. Sync the parent directory to ensure the rename is persisted
|
||||
|
||||
### 2. Directory Synchronization
|
||||
|
||||
Directory operations are also synchronized:
|
||||
|
||||
```rust
|
||||
/// Creates directories with fsync
|
||||
pub async fn create_dir_with_sync<P: AsRef<Path>>(path: P) -> Result<(), IoError>
|
||||
```
|
||||
|
||||
This ensures that:
|
||||
1. Directories are properly created
|
||||
2. Directory entries are persisted to disk
|
||||
3. Parent directories are also synchronized
|
||||
|
||||
### 3. Rename and Delete Operations
|
||||
|
||||
Renames and delete operations follow the same pattern:
|
||||
|
||||
```rust
|
||||
/// Renames a file or directory with proper syncing
|
||||
pub async fn rename_with_sync<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<(), IoError>
|
||||
|
||||
/// Removes a file with directory syncing
|
||||
pub async fn remove_file_with_sync<P: AsRef<Path>>(path: P) -> Result<(), IoError>
|
||||
```
|
||||
|
||||
These operations ensure that:
|
||||
1. The operation itself is completed
|
||||
2. The parent directory entry is updated and synchronized
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Implementing fsync on Files
|
||||
|
||||
```rust
|
||||
// Write file content
|
||||
file.write_all(contents).await?;
|
||||
|
||||
// Ensure data is synced to disk
|
||||
file.flush().await?;
|
||||
file.sync_all().await?;
|
||||
```
|
||||
|
||||
The `sync_all()` call is critical as it instructs the operating system to flush data and metadata to the physical storage device.
|
||||
|
||||
### Implementing fsync on Directories
|
||||
|
||||
```rust
|
||||
// Sync a directory to ensure its contents (entries) are durable
|
||||
async fn sync_directory<P: AsRef<Path>>(path: P) -> Result<(), IoError> {
|
||||
let dir_file = OpenOptions::new().read(true).open(path).await?;
|
||||
dir_file.sync_all().await
|
||||
}
|
||||
```
|
||||
|
||||
This is essential after operations that modify directory entries, such as creating, renaming, or deleting files.
|
||||
|
||||
## Usage in the Codebase
|
||||
|
||||
The `FileSystemUtils` service is integrated throughout OxiCloud's file operations:
|
||||
|
||||
### In File Write Repository
|
||||
|
||||
```rust
|
||||
// Write the file to disk using atomic write with fsync
|
||||
tokio::time::timeout(
|
||||
self.config.timeouts.file_write_timeout(),
|
||||
FileSystemUtils::atomic_write(&abs_path, &content)
|
||||
).await
|
||||
```
|
||||
|
||||
### In File Move Operations
|
||||
|
||||
```rust
|
||||
// Move the file physically with fsync
|
||||
time::timeout(
|
||||
self.config.timeouts.file_timeout(),
|
||||
FileSystemUtils::rename_with_sync(&old_abs_path, &new_abs_path)
|
||||
).await
|
||||
```
|
||||
|
||||
### For Directory Creation
|
||||
|
||||
```rust
|
||||
// Ensure the parent directory exists with proper syncing
|
||||
self.ensure_parent_directory(&abs_path).await?;
|
||||
|
||||
// Implementation uses FileSystemUtils
|
||||
async fn ensure_parent_directory(&self, abs_path: &PathBuf) -> FileRepositoryResult<()> {
|
||||
if let Some(parent) = abs_path.parent() {
|
||||
time::timeout(
|
||||
self.config.timeouts.dir_timeout(),
|
||||
FileSystemUtils::create_dir_with_sync(parent)
|
||||
).await
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
By implementing these safety measures, OxiCloud provides:
|
||||
|
||||
1. **Data Durability**: Critical data is properly synchronized to persistent storage
|
||||
2. **Crash Resilience**: The system can recover from unexpected failures without data loss
|
||||
3. **Consistency**: File operations maintain a consistent file system state
|
||||
4. **Atomic Operations**: File writes appear as all-or-nothing operations
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
These safety measures do have some performance impact, as synchronizing to disk is more expensive than buffered writes. However, OxiCloud:
|
||||
|
||||
1. Applies these measures only to critical operations
|
||||
2. Uses timeouts to prevent operations from blocking indefinitely
|
||||
3. Implements parallel processing for large files
|
||||
|
||||
The safety-performance tradeoff favors safety for critical data while still maintaining good performance for most operations.
|
||||
@@ -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