Cuando una tabla supera los decenas de millones de filas y las consultas suelen filtrar por un rango (fechas) o un grupo (tenant_id), el particionamiento divide la tabla física en piezas más pequeñas que el planificador puede recorrer selectivamente. PostgreSQL ofrece particionamiento declarativo desde la versión 10 con mejoras significativas en cada release.
Tipos de particionamiento
PostgreSQL soporta tres estrategias de particionamiento:
PARTITION BY RANGE: por intervalos contiguos (fechas, IDs).PARTITION BY LIST: por valores discretos (códigos de país, tipos de evento).PARTITION BY HASH: por hash uniforme, útil para distribuir carga.
Y se pueden combinar en subparticiones para escenarios complejos: por ejemplo, particionar por mes y dentro de cada mes por país.
Particionamiento por RANGE: el caso típico
El uso más común es particionar tablas grandes de eventos o pedidos por fecha:
CREATE TABLE pedidos (
id BIGSERIAL,
cliente_id BIGINT NOT NULL,
total NUMERIC(12,2) NOT NULL,
creado_en TIMESTAMPTZ NOT NULL,
PRIMARY KEY (id, creado_en)
) PARTITION BY RANGE (creado_en);
CREATE TABLE pedidos_2026_01 PARTITION OF pedidos
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE pedidos_2026_02 PARTITION OF pedidos
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
CREATE TABLE pedidos_default PARTITION OF pedidos DEFAULT;
La primary key debe incluir la columna de partición. Esto es una restricción del particionamiento declarativo: las constraints deben poder evaluarse dentro de cada partición individualmente.
Las consultas con WHERE creado_en BETWEEN '2026-01-15' AND '2026-01-20' solo escanean pedidos_2026_01 gracias a la partition pruning del planificador. El resto de particiones se ignora completamente.
Particionamiento por LIST y HASH
LIST permite agrupar filas por categorías discretas:
CREATE TABLE eventos (
id BIGSERIAL,
pais TEXT NOT NULL,
payload JSONB
) PARTITION BY LIST (pais);
CREATE TABLE eventos_es PARTITION OF eventos FOR VALUES IN ('ES');
CREATE TABLE eventos_us PARTITION OF eventos FOR VALUES IN ('US', 'CA', 'MX');
CREATE TABLE eventos_otros PARTITION OF eventos DEFAULT;
HASH distribuye uniformemente sin necesidad de saber los valores de antemano:
CREATE TABLE sesiones (
user_id BIGINT NOT NULL,
token TEXT NOT NULL,
PRIMARY KEY (user_id, token)
) PARTITION BY HASH (user_id);
CREATE TABLE sesiones_p0 PARTITION OF sesiones FOR VALUES WITH (modulus 4, remainder 0);
CREATE TABLE sesiones_p1 PARTITION OF sesiones FOR VALUES WITH (modulus 4, remainder 1);
CREATE TABLE sesiones_p2 PARTITION OF sesiones FOR VALUES WITH (modulus 4, remainder 2);
CREATE TABLE sesiones_p3 PARTITION OF sesiones FOR VALUES WITH (modulus 4, remainder 3);
HASHes ideal cuando necesitas paralelismo de escritura uniforme y no tienes una columna con distribución natural útil.
Subparticionamiento
Es legítimo combinar estrategias para tablas muy grandes:
CREATE TABLE eventos (
id BIGSERIAL,
creado_en TIMESTAMPTZ NOT NULL,
pais TEXT NOT NULL,
payload JSONB
) PARTITION BY RANGE (creado_en);
CREATE TABLE eventos_2026_01 PARTITION OF eventos
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01')
PARTITION BY LIST (pais);
CREATE TABLE eventos_2026_01_es PARTITION OF eventos_2026_01 FOR VALUES IN ('ES');
CREATE TABLE eventos_2026_01_us PARTITION OF eventos_2026_01 FOR VALUES IN ('US');
Útil para datasets enormes con dos dimensiones de filtrado frecuente. Cuidado: cada partición es una tabla física, y demasiadas particiones degradan el planificador.
ATTACH y DETACH sin downtime
Una de las características más útiles del particionamiento moderno es la capacidad de adjuntar y desadjuntar particiones online:
-- Crear la nueva particion como tabla independiente
CREATE TABLE pedidos_2026_03 (LIKE pedidos INCLUDING ALL);
-- Cargar datos en bloque sin afectar a pedidos
COPY pedidos_2026_03 FROM '/tmp/pedidos_marzo.csv' CSV;
-- Adjuntarla a la tabla padre
ALTER TABLE pedidos
ATTACH PARTITION pedidos_2026_03
FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');
Antes de PostgreSQL 12 esto adquiría un ACCESS EXCLUSIVE lock. Desde la 12 solo se necesita un SHARE UPDATE EXCLUSIVE, lo que permite seguir leyendo y escribiendo en otras particiones mientras se incorpora la nueva.
Para desadjuntar una partición vieja (por ejemplo, archivar pedidos del año pasado):
ALTER TABLE pedidos DETACH PARTITION pedidos_2025_03 CONCURRENTLY;
-- Ahora pedidos_2025_03 es una tabla independiente.
-- Puedes archivarla, comprimirla o moverla a otro tablespace.
ALTER TABLE pedidos_2025_03 SET TABLESPACE archivo_lento;
La modalidad
CONCURRENTLYañadida en PostgreSQL 14 evita bloqueos largos en tablas con tráfico activo.
Automatización con pg_partman
Mantener particiones manualmente es tedioso. La extensión pg_partman automatiza la creación de particiones futuras y la eliminación de antiguas según políticas:
CREATE EXTENSION IF NOT EXISTS pg_partman SCHEMA partman;
SELECT partman.create_parent(
p_parent_table := 'public.pedidos',
p_control := 'creado_en',
p_type := 'native',
p_interval := '1 month',
p_premake := 4
);
UPDATE partman.part_config
SET retention = '12 months',
retention_keep_table = false
WHERE parent_table = 'public.pedidos';
Con esa configuración, pg_partman mantiene siempre cuatro particiones futuras y elimina las anteriores a 12 meses. Un job de pg_cron o un crontab invoca partman.run_maintenance() periódicamente:
SELECT cron.schedule('partman_maintenance', '0 2 * * *',
'SELECT partman.run_maintenance();');
Sin
pg_partmante toca crear cada mes una partición a mano y recordar archivar las viejas. Con la extensión es trabajo cero después de la configuración inicial.
Diferencias con el modelo de inheritance
Antes de PostgreSQL 10 el particionamiento se implementaba con inheritance: una tabla padre vacía y tablas hijas que heredaban su esquema, junto con triggers para enrutar INSERTs a la partición correcta. Ese modelo aún funciona, pero el declarativo es superior en casi todo:
| Aspecto | Inheritance (legacy) | Declarativo (moderno) |
|---------|----------------------|------------------------|
| Sintaxis | INHERITS (...) y triggers | PARTITION BY ... |
| Routing de INSERT | Triggers manuales | Automático |
| Constraint exclusion | Sí, manual | Sí, partition pruning |
| ATTACH online | No | Sí |
| Foreign keys hacia | No | Sí desde PG 12 |
| Particiones default | No | Sí |
Si encuentras un esquema con
INHERITS, considera migrarlo. Es un proyecto que requiere cuidado pero merece la pena por la limpieza operacional que aporta.
Cuándo NO particionar
El particionamiento añade complejidad y no siempre mejora el rendimiento:
- Tablas con menos de un millón de filas: el overhead supera el beneficio.
- Consultas que rara vez filtran por la columna de partición.
- Necesidad frecuente de joins entre particiones: el planificador no siempre optimiza igual.
- Esquemas con muchas foreign keys hacia la tabla particionada en versiones antiguas.
flowchart LR
A[Pedidos<br/>tabla particionada] --> B[Pedidos_2026_01]
A --> C[Pedidos_2026_02]
A --> D[Pedidos_2026_03]
B --> E[(Disco)]
C --> E
D --> E
F[Query con<br/>WHERE fecha BETWEEN] --> G{Partition<br/>pruning}
G -->|Solo enero| B
El particionamiento bien aplicado convierte tablas inmanejables en estructuras que escalan horizontalmente dentro del mismo servidor: cada partición tiene sus propios índices, sus propias estadísticas y se puede mover a tablespaces distintos según el patrón de acceso.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, SQL es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de SQL
Explora más contenido relacionado con SQL y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Crear tablas particionadas declarativas con RANGE, LIST, HASH y subparticiones. Adjuntar y desadjuntar particiones sin bloquear la tabla padre. Configurar pg_partman para crear particiones futuras y eliminar antiguas. Conocer las diferencias entre particionamiento declarativo y el viejo modelo de inheritance. Saber cuándo el particionamiento mejora el rendimiento y cuándo lo empeora.
Cursos que incluyen esta lección
Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje