Database Migration Report — 2026-06-06

timestamp timestamptz

Opção D — 77 colunas, 9 módulos, 190k rows corrigidos.
Validado contra dump de produção com zero discrepâncias.

✓ Schema limpo ✓ Spot-checks OK ✓ Idempotente
01

Contexto

Problema

Migração MySQL → PostgreSQL herdou colunas timestamp (sem timezone). Em 2026-05-20, APP_TIMEZONE mudou de America/Sao_Paulo para UTC.

Consequência

77 colunas com dados em duas eras: ~3 anos em horário SP + ~2 semanas em UTC. Bug de ±3h no display de datas (ex: check-in numérico quebrado, commit e9e61fd8).

2023 → 2026-05-20
Dados armazenados em America/Sao_Paulo
2026-05-20 20:04:16 UTC
APP_TIMEZONE switch — commit c35082ea
2026-05-20 → presente
Dados armazenados em UTC
02

Estratégia — Opção D

Interpretar tudo como São Paulo, depois corrigir a janela pós-switch. UPDATE em ~2 semanas de dados (não em ~3 anos).

Antes
timestamp
sem timezone
2025-03-15 14:00:00
(era SP? era UTC? 🤷)
Step 1 →
ALTER Migration
ALTER COLUMN col TYPE timestamptz
USING col AT TIME ZONE
'America/Sao_Paulo'
✓ Pré-switch: correto
✗ Pós-switch: +3h off
Step 2 →
Artisan Command
UPDATE t SET col =
col - interval '3 hours'
WHERE col >= CUTOFF
✓ Pós-switch: corrigido
Depois
timestamptz
com timezone
2025-03-15 17:00:00+00
UTC absoluto ✓
03

Categorias de Colunas

A Pré-switch, Laravel-managed
ALTER SP + UPDATE

Bulk das colunas. Preenchidas por now(), timestamps(), softDeletes(). Requer 2 steps.

Colunas com datas futuras (starts_at, ended_at, expires_at) usam fixScope: 'row' — WHERE created_at >= CUTOFF
B Discord API
ALTER UTC, sem UPDATE

Dados vindos da API do Discord. Sempre foram UTC independente de APP_TIMEZONE.

joined_at · premium_since · communication_disabled_until
C Pós-switch only
ALTER UTC, sem UPDATE

Tabelas criadas após o switch. Só contêm dados em UTC.

twitch_event_logs · twitch_subscriptions · user_profiles
D Já timestamptz
SKIP

Colunas que já usam timestamptz. Idempotência pula automaticamente.

moderation_* · characters_wallet · addresses · sent_at · occurred_at
04

Artefatos Criados

01
9 ALTER Migrations
Schema change · 1 por módulo · Idempotente

Converte colunas de timestamp para timestamptz usando AT TIME ZONE. Checa information_schema antes de alterar.

~65s no dump local
02
Comando Artisan
maintenance:fix-post-switch-timestamps

Corrige dados pós-switch subtraindo 3h. Progress display com Laravel Prompts, timing por coluna, summary table.

--dry-run --module= --table=
190,305 rows em 3.3s
03
49 Migrações Originais Atualizadas
Fresh installs criam timestamptz diretamente
timestamps()timestampsTz() softDeletes()softDeletesTz() dateTime()dateTimeTz()
05

Módulos — Breakdown

Módulo Tabelas Colunas Rows Fix ALTER
identity7182,071~2s
activity123038,156~58s
gamification614960~0.3s
integration-discord718149,084~3s
integration-twitch24~0.04s
community5140~0.08s
economy240~0.03s
profile12~0.04s
main51134~0.08s
TOTAL47115190,305~65s
06

Procedimento de Produção

manutenção — produção
⚠ Pré-requisitos: backup confirmado
$ php artisan down
Application is now in maintenance mode.
$ parar bot Discord
$ php artisan migrate
9 ALTER migrations... DONE
$ php artisan maintenance:fix-post-switch-timestamps
190,305 rows fixed... DONE
$ verificar checklist (spot-checks SQL)
$ php artisan up
Application is now live.
$ reiniciar bot Discord
💡
Estimativa prod (2vCPU+2GB): ALTER migrations ~2-5min (activity é o gargalo: 58s local). Comando de dados ~10-20s. Total: ~3-6 minutos de downtime.
07

Validação

Schema limpo
0 colunas timestamp without time zone restantes
Pré-switch: horários SP corretos
16:25 SP, 14:54 SP, 14:36 SP — atividade humana ✓
Pós-switch: sem shift de +3h
Continuidade: 16:25 SP → 17:15 SP (sem salto)
Discord API: joined_at consistente
joined_at e created_at diferem por ±1s (tempo de processamento)
Idempotência
Re-executar tudo = no-op. Migrations checam tipo, comando retorna 0 rows.
Testes & análise estática
620/620 tests · pint passed · phpstan 0 errors
08

Commits

4abdd632
refactoruse timestampTz in original migrations
49 files changed
71bf97b8
featadd ALTER migrations for timestamptz
9 files created
8a74bce9
featadd post-switch timestamp fix command
1 file created
b50351e8
chorecleanup and guideline for timestamptz
3 files changed
09

Ajustes Extras

Removida
provider_tokens — tabela dropada em migration anterior
Dropadas
tmi_cluster_* — 3 tabelas órfãs (pacote removido)
Corrigido
Review.received_at — cast 'timestamp''datetime'
Adicionada
Guideline 05-timezone-aware-dates para LLM agents