DEV Community

Cover image for Particionamiento mensual y rotación automática de tablas en PostgreSQL (AWS RDS)
Ivaj O'Franc
Ivaj O'Franc

Posted on

Particionamiento mensual y rotación automática de tablas en PostgreSQL (AWS RDS)

Particionamiento mensual y rotación automática de tablas en PostgreSQL (AWS RDS)

🇬🇧 Also read this post in English


🧩 Introducción

Este tutorial explica cómo transformar la tabla my_table de tu base de datos PostgreSQL (en AWS RDS) a una estructura particionada por mes usando rango sobre UTC, con rotación automática y mínima caída de servicio. El proceso está pensado para mantener la compatibilidad con las aplicaciones existentes y facilitar el mantenimiento y borrado automático de datos antiguos según las políticas de retención.


⚙️ Entorno usado

  • PostgreSQL 14.17 (AWS RDS)
  • TimeZone: UTC
  • Tabla principal: public.my_table (nombres modificados para anonimidad)
  • Automatización vía Python (AWS Lambda) y/o pg_cron

🧩 Problema encontrado

  • La tabla my_table era no particionada y acumulaba datos rápidamente.
  • Necesidad de dividir los datos por mes (UTC) sin detener los servicios.
  • Se debe respetar la retención: 2 meses.
  • Debe ser posible crear y eliminar particiones de forma automática.
  • Renombrar índices y constraints antiguos para liberar nombres reservados y evitar conflictos durante la migración.
  • Compatible con scripts y aplicaciones actuales.

🔧 Tutorial paso a paso

1. Renombrar la tabla actual y liberar nombres

-- Renombrar tabla my_table a my_table_old
ALTER TABLE public.my_table RENAME TO my_table_old;

-- Renombrar índice primario (ejemplo)
ALTER INDEX public.my_table_pkey RENAME TO my_table_pkey_old;
Enter fullscreen mode Exit fullscreen mode

2. Crear la nueva tabla particionada

-- Crear tabla audit_log parent particionada por RANGE en la columna "event_time"
CREATE TABLE public.audit_log (
    event_time timestamptz NOT NULL,
    action_type text NOT NULL,
    user_id int,
    details text,
    request_payload text,
    query_params text,
    app_instance text,
    response_payload text,
    log_id int NOT NULL,
    duration_ms int,
    source_ip text,
    CONSTRAINT audit_log_pkey PRIMARY KEY (log_id, event_time)
) PARTITION BY RANGE (event_time);
Enter fullscreen mode Exit fullscreen mode

3. Crear los partitions mensuales necesarias

-- Partición para el mes anterior
CREATE TABLE public.audit_log_2025_09 PARTITION OF public.audit_log
    FOR VALUES FROM ('2025-09-01 00:00:00+00') TO ('2025-10-01 00:00:00+00');

-- Partición para el mes actual
CREATE TABLE public.audit_log_2025_10 PARTITION OF public.audit_log
    FOR VALUES FROM ('2025-10-01 00:00:00+00') TO ('2025-11-01 00:00:00+00');

-- Partición para el mes siguiente
CREATE TABLE public.audit_log_2025_11 PARTITION OF public.audit_log
    FOR VALUES FROM ('2025-11-01 00:00:00+00') TO ('2025-12-01 00:00:00+00');
Enter fullscreen mode Exit fullscreen mode

4. Crear índices globales si se requiere

-- Ejemplo de índice global en campo "date" 
CREATE INDEX my_table_date_idx ON public.my_table(date);
Enter fullscreen mode Exit fullscreen mode

5. (Opcional) Migrar algunos datos de prueba

-- Insertar registros recientes para pruebas (sin histórico completo)
INSERT INTO public.audit_log (
    event_time, action_type, user_id, details, request_payload, query_params, app_instance, response_payload, log_id, duration_ms, source_ip
)
SELECT
    event_time, action_type, user_id, details, request_payload, query_params, app_instance, response_payload, log_id, duration_ms, source_ip
FROM public.audit_log_old
WHERE event_time >= '2025-10-01 00:00:00+00';
Enter fullscreen mode Exit fullscreen mode

6. Actualiza tus scripts y aplicaciones

Confirma que modificaste las referencias necesarias para operar sobre la nueva tabla my_table, ahora particionada. Si usabas my_table, los nombres se deben conservar salvo si hubo requerimientos extra.

7. Automatiza la rotación de particiones

Implementa el mantenimiento con pg_cron o Lambda + Python:

Ejemplo función de rotación en PL/pgSQL

CREATE OR REPLACE FUNCTION audit_log_rotate(retention_months INT, horizon_months INT) RETURNS VOID AS $$
DECLARE
    current_month date := date_trunc('month', now() AT TIME ZONE 'UTC');
    first_keep date := current_month - INTERVAL '1 month' * retention_months;
    last_create date := current_month + INTERVAL '1 month' * horizon_months;
BEGIN
    -- Crear particiones futuras
    FOR m IN 0..horizon_months LOOP
        EXECUTE format(
            'CREATE TABLE IF NOT EXISTS public.audit_log_%s PARTITION OF public.audit_log FOR VALUES FROM (%L) TO (%L);',
            to_char(current_month + INTERVAL '1 month' * m, 'YYYY_MM'),
            current_month + INTERVAL '1 month' * m,
            current_month + INTERVAL '1 month' * (m+1)
        );
    END LOOP;
    -- Borrar antiguas
    FOR r IN 1..retention_months LOOP
        EXECUTE format(
            'DROP TABLE IF EXISTS public.audit_log_%s CASCADE;',
            to_char(first_keep - INTERVAL '1 month' * r, 'YYYY_MM')
        );
    END LOOP;
END;
$$ LANGUAGE plpgsql;
Enter fullscreen mode Exit fullscreen mode

Programar ejecución vía pg_cron todos los días:

SELECT cron.schedule('rotate_audit_log', '5 0 1 * *', 'SELECT audit_log_rotate(2,2);');
Enter fullscreen mode Exit fullscreen mode

O implementa la lógica en AWS Lambda con Python y pg8000, conectando a cada base con los credentials seguros (usando AWS Secrets Manager).


💡 Recomendaciones

  • Renombra todos los objetos antes del swap para evitar conflictos de nombres.
  • Automatiza la rotación: crea las particiones futuras y borra las antiguas según retención.
  • Valida que las aplicaciones y scripts no dependan de particiones específicas; opera siempre sobre el parent.
  • Protege y centraliza los parámetros de mantenimiento (retención, horizon, etc.).

🔗 Enlaces útiles

Top comments (0)