- Un artículo que recopila patrones prácticos para usar Postgres de forma más productiva y segura
- Cada patrón es pequeño, pero al acumularse genera una gran diferencia
Uso de claves primarias UUID
- Como los UUID son aleatorios, tienen desventajas en términos de ordenamiento y rendimiento de índices
- Ocupan más espacio que los ID numéricos
- Pero tienen las siguientes ventajas
- Se pueden generar UUID sin conectarse a la BD
- Se pueden exponer externamente de forma segura
- Se puede usar
gen_random_uuid() para generar automáticamente UUID como clave primaria
Agregar siempre los campos created_at y updated_at
- Al depurar, es muy útil saber cuándo se creó y modificó un registro
updated_at puede configurarse para actualizarse automáticamente mediante un trigger
- La función se crea una sola vez, y el trigger debe aplicarse a cada tabla
Configurar on update/delete restrict en claves foráneas
- Al definir restricciones de clave foránea, conviene usar siempre
on update restrict on delete restrict
- Esto evita que se produzcan eliminaciones en cascada por error al borrar datos
- El almacenamiento es barato, pero recuperar datos es muy difícil, por lo que conviene ser conservador
Se recomienda usar esquemas
- El esquema predeterminado es
public, pero cuando la aplicación crece conviene separarla en esquemas propios
- Los esquemas funcionan como namespaces y permiten joins incluso entre esquemas distintos
- Cuantas más tablas haya, más conviene aprovechar los esquemas para mejorar legibilidad y mantenimiento
Usar el patrón de tablas enum
- En lugar de usar el tipo enum de PostgreSQL o un check constraint, usar tablas enum resulta más flexible
- Si los valores enum se administran en una tabla separada, es fácil agregar metadatos o extenderlos
- Las restricciones se mantienen al referenciar los valores de la tabla enum mediante claves foráneas
Nombrar las tablas en singular
- Es preferible nombrar las tablas en singular y no en plural
- Al escribir consultas, el singular es más claro, y el plural puede causar confusión semántica o posesiva
Nombrar las tablas de unión de forma mecánica
- Para relaciones muchos a muchos, es más seguro y claro nombrar la tabla de unión concatenando los nombres de ambas tablas
- Ejemplo:
person_pet
- Agregar un índice único sobre la combinación para evitar duplicados
Usar soft delete en lugar de borrar
- En vez de eliminar realmente los datos, conviene usar un campo timestamp como
revoked_at que indique cuándo se eliminaron
- Esto permite rastrear no solo si se eliminó, sino también cuándo ocurrió
- Un timestamp aporta más información que un valor booleano
Representar el estado (Status) con una tabla de logs
- En lugar de expresar el estado con una sola columna, se guarda el historial de cambios de estado en una tabla separada
- El momento en que ocurre el estado se indica explícitamente con la columna
valid_at
- Para consultar rápidamente el estado más reciente, se configuran un flag
latest y un índice único + trigger
- Esto es útil en procesamiento de eventos asíncronos o situaciones donde el orden puede mezclarse
Agregar system_id a filas especiales
- Además de las tablas enum, a veces se necesitan ciertas "filas del sistema"
- Se agrega un campo de texto
system_id nullable y se configura un índice único
system_id permite consultar con claridad una fila específica
Usar las vistas (View) al mínimo
- Las vistas son útiles para abstraer consultas complejas, pero son difíciles de mantener
- Si se elimina una columna, hay que volver a crear la vista
- Si se crean vistas sobre vistas, aparecen problemas de rendimiento y legibilidad
- Conviene usarlas con cuidado y solo cuando sea necesario
Aprovechar activamente las consultas JSON
- Postgres es muy potente no solo para almacenar JSON, sino también para devolver JSON desde consultas
- Puede devolver relaciones anidadas en formato JSON con una sola consulta
- Permite traer todos los datos necesarios de una vez sin el problema de N+1
- Desventajas: pérdida de información de tipos y necesidad de cargar todos los datos en memoria de una sola vez
- Las ventajas en rendimiento o estructura son mayores
4 comentarios
> Nombrar las tablas de unión de forma mecánica
Creo que está bueno que exista una regla así para ponerles nombre~
Si consideras UUID7, ¿no sería posible ordenar cronológicamente?
Parece que también valdría la pena echarle un vistazo al artículo sobre usar UUID como clave primaria en PostgreSQL.
Qué buena forma de agregar un timestamp al hacer soft delete.
Si usas UUID como clave primaria, no se puede ordenar por tiempo, así que también podría ser buena idea usar un snowflake ID o ULID. Aunque en ese caso cada servidor tendría que tener su propio sequence number.