Estratégias de hot e cold data: tiering de armazenamento para dados históricos

1. Conceitos Fundamentais de Tiering de Armazenamento

O tiering de armazenamento é uma estratégia que classifica dados com base em sua frequência de acesso, latência necessária e criticidade operacional. Os três tiers principais são:

  • Hot data: Dados acessados frequentemente (milissegundos de latência), alta criticidade. Exemplo: transações do dia corrente.
  • Warm data: Dados acessados ocasionalmente (segundos de latência), criticidade moderada. Exemplo: transações do último trimestre.
  • Cold data: Dados raramente acessados (minutos a horas de latência), baixa criticidade. Exemplo: transações com mais de 5 anos.

A separação entre tiers reduz custos de armazenamento em até 80% e melhora a performance operacional, pois índices e buffers ficam dedicados aos dados quentes.

Exemplo prático: Um sistema financeiro mantém dados de transações:
- Hot: mês corrente em SSD (acesso <1ms)
- Warm: últimos 12 meses em HDD (acesso <10ms)
- Cold: 5+ anos em object storage (acesso <30s)

-- Estrutura de tiering baseada em data
CREATE TABLE transacoes (
    id SERIAL PRIMARY KEY,
    valor DECIMAL(15,2),
    data_transacao TIMESTAMP,
    tipo VARCHAR(20)
) PARTITION BY RANGE (data_transacao);

-- Partição hot (mês corrente)
CREATE TABLE transacoes_2025_03
    PARTITION OF transacoes
    FOR VALUES FROM ('2025-03-01') TO ('2025-04-01')
    TABLESPACE ssd_fast;

-- Partição warm (últimos 12 meses)
CREATE TABLE transacoes_2024_03
    PARTITION OF transacoes
    FOR VALUES FROM ('2024-03-01') TO ('2024-04-01')
    TABLESPACE hdd_standard;

2. Arquiteturas Comuns de Tiering em Bancos Relacionais

O particionamento por data (range partitioning) é a base para movimentação entre tiers. No PostgreSQL, o uso de herança de tabelas permite isolar dados históricos sem impacto no schema principal.

Migração manual vs. automatizada:
- Manual: Scripts SQL executados em janelas de manutenção
- Automatizada: Jobs com pg_cron ou triggers que movem partições ao final de cada mês

-- Exemplo de herança para dados cold
CREATE TABLE transacoes_cold (LIKE transacoes INCLUDING ALL);
ALTER TABLE transacoes_cold ADD CONSTRAINT ano_check 
    CHECK (data_transacao < '2024-01-01');

-- Migração automatizada via pg_cron
SELECT cron.schedule('migrate-cold', '0 3 1 * *', $$
    INSERT INTO transacoes_cold 
    SELECT * FROM transacoes 
    WHERE data_transacao < NOW() - INTERVAL '3 years';
    DELETE FROM transacoes 
    WHERE data_transacao < NOW() - INTERVAL '3 years';
$$);

3. Estratégias de Movimentação de Dados (Hot → Cold)

ETL reverso: Exportar dados cold para arquivos parquet compressados e removê-los do banco principal. Isso libera espaço e reduz custos de backup.

Foreign Data Wrappers (FDW): Acessar dados cold sem movê-los fisicamente, mantendo a capacidade de consulta.

Políticas de retenção: Definir janelas temporais para cada tier usando tabelas temporárias.

-- ETL reverso para parquet (via psql + Python)
-- 1. Exportar dados cold
COPY (
    SELECT * FROM transacoes 
    WHERE data_transacao < '2023-01-01'
) TO '/tmp/cold_transacoes.csv' WITH CSV;

-- 2. Converter para parquet compressado (comando externo)
-- duckdb -c "COPY (SELECT * FROM read_csv('/tmp/cold_transacoes.csv')) 
-- TO '/cold_storage/transacoes_2022.parquet' (FORMAT PARQUET, COMPRESSION ZSTD);"

-- 3. Remover do banco principal
DELETE FROM transacoes WHERE data_transacao < '2023-01-01';

-- FDW para acessar dados cold
CREATE FOREIGN DATA WRAPPER parquet_fdw HANDLER parquet_fdw_handler;
CREATE SERVER cold_storage FOREIGN DATA WRAPPER parquet_fdw 
    OPTIONS (location '/cold_storage/');
CREATE FOREIGN TABLE transacoes_cold_fdw (
    id INTEGER,
    valor DECIMAL(15,2),
    data_transacao TIMESTAMP,
    tipo VARCHAR(20)
) SERVER cold_storage OPTIONS (filename 'transacoes_2022.parquet');

4. Armazenamento Frio: Opções e Trade-offs

Opção Custo/GB/mês Latência Compressão Recuperação
S3 Standard $0.023 5-10ms Automática Imediata
S3 Glacier $0.004 1-5min Automática 1-12h
MinIO (on-prem) $0.010 10-20ms Configurável Imediata
Fita magnética $0.001 1-24h Manual Dias

Bancos analíticos dedicados: ClickHouse e DuckDB oferecem compressão colunar (10-20x) e consultas otimizadas para dados frios.

-- Configuração de cold storage no ClickHouse
CREATE TABLE transacoes_cold_clickhouse (
    id Int32,
    valor Decimal(15,2),
    data_transacao DateTime,
    tipo String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(data_transacao)
ORDER BY (data_transacao, id)
SETTINGS storage_policy = 'cold_policy';

-- Política de tiering no MinIO/S3
-- minio-client cp /cold_storage/transacoes_2022.parquet s3://cold-bucket/
-- aws s3api put-object-tagging --bucket cold-bucket --key transacoes_2022.parquet \
--   --tagging '{"TagSet": [{"Key": "tier", "Value": "cold"}]}'

5. Indexação e Performance em Dados Frios

Índices em dados cold são desnecessários e consomem espaço. Use índices parciais para dados quentes e BRIN para dados históricos.

BRIN (Block Range Index): Ideal para dados ordenados temporalmente, ocupa 1% do espaço de um B-tree.

-- Índice parcial apenas para dados quentes
CREATE INDEX idx_transacoes_hot ON transacoes (data_transacao)
    WHERE data_transacao >= NOW() - INTERVAL '30 days';

-- Índice BRIN para dados históricos (cold)
CREATE INDEX idx_transacoes_cold_brin ON transacoes_cold 
    USING BRIN (data_transacao)
    WITH (pages_per_range = 32);

-- Compressão de páginas em dados frios
-- pg_repack para reorganizar sem lock
-- pg_repack -t transacoes_cold -o 'fillfactor=50, autovacuum_enabled=off'

6. Monitoramento e Automação do Ciclo de Vida

Use métricas do pg_stat_user_tables para identificar dados que se tornaram frios e ferramentas como pg_partman para automação.

-- Identificar tabelas candidatas a tiering
SELECT schemaname, tablename, 
       n_tup_ins + n_tup_upd + n_tup_del as total_writes,
       seq_scan + idx_scan as total_reads,
       n_live_tup as row_count
FROM pg_stat_user_tables
WHERE (seq_scan + idx_scan) < 100 
  AND (n_tup_ins + n_tup_upd + n_tup_del) > 10000
ORDER BY total_reads ASC;

-- Configuração pg_partman para retenção automática
SELECT partman.create_parent(
    p_parent_table := 'public.transacoes',
    p_control := 'data_transacao',
    p_type := 'native',
    p_interval := '1 month',
    p_premake := 3
);

-- Política de retenção: manter 36 partições (3 anos)
UPDATE partman.part_config 
SET retention = '36 months', 
    retention_keep_table = false
WHERE parent_table = 'public.transacoes';

7. Casos de Uso e Exemplos Práticos

Sistema de logs de aplicação:

-- Hot: últimas 24h em memória (Redis)
-- Warm: 30 dias em PostgreSQL particionado por dia
-- Cold: histórico em S3 compressado

CREATE TABLE logs (
    id UUID DEFAULT gen_random_uuid(),
    timestamp TIMESTAMPTZ DEFAULT NOW(),
    level TEXT,
    message TEXT,
    service TEXT
) PARTITION BY RANGE (timestamp);

-- Partição diária para warm
CREATE TABLE logs_20250328 
    PARTITION OF logs 
    FOR VALUES FROM ('2025-03-28') TO ('2025-03-29')
    TABLESPACE ssd_fast;

-- Exportação diária para cold storage
SELECT cron.schedule('export-logs-cold', '0 4 * * *', $$
    COPY (
        SELECT * FROM logs 
        WHERE timestamp < NOW() - INTERVAL '30 days'
    ) TO PROGRAM 'gzip > /cold_backup/logs_$(date -d "30 days ago" +%Y%m%d).csv.gz';
    DELETE FROM logs WHERE timestamp < NOW() - INTERVAL '30 days';
$$);

Dados de sensores IoT:

-- Tiering por dispositivo e timestamp
CREATE TABLE sensor_data (
    device_id INT,
    timestamp TIMESTAMPTZ,
    temperatura DECIMAL(5,2),
    umidade DECIMAL(5,2)
) PARTITION BY RANGE (timestamp);

-- Hot: dados das últimas 24h em tabela não particionada
CREATE TABLE sensor_data_hot (
    device_id INT,
    timestamp TIMESTAMPTZ,
    temperatura DECIMAL(5,2),
    umidade DECIMAL(5,2)
) TABLESPACE ram_disk;

-- Migração para warm a cada hora
CREATE OR REPLACE FUNCTION migrate_sensor_data()
RETURNS void AS $$
BEGIN
    INSERT INTO sensor_data 
    SELECT * FROM sensor_data_hot 
    WHERE timestamp < NOW() - INTERVAL '1 hour';

    DELETE FROM sensor_data_hot 
    WHERE timestamp < NOW() - INTERVAL '1 hour';
END;
$$ LANGUAGE plpgsql;

Referências