Análise para a nova estrutura da Gea Sepog:
- Estruturar o ambiente via Docker Compose com Ruby/Rails, Redis, Sidekiq e Metabase App.
- Escrita transacional no PostgreSQL Writer.
- Replicação física para PostgreSQL Reader.
- Metabase consultando somente a réplica com usuário read-only.
Serviços Docker Compose:
railssidekiqpokedex-redispokedex-postgres-writer(porta host5433)pokedex-postgres-reader(porta host5434)metabase-postgres(porta host5435)metabase(porta host3002, configurável porMETABASE_PORT)
pokedex-postgres-writer: banco primário (read/write). Sobe com scripts de init que criam usuários e configuração de replicação.- Scripts executados no startup do Writer (
/docker-entrypoint-initdb.d, somente no primeiro boot com volume vazio): docker/postgres/writer/init/create-replication-user.sh: cria o usuário de replicação (replicator).docker/postgres/writer/init/configure-replication-hba.sh: adiciona regra nopg_hba.confpara permitir conexão de replicação.docker/postgres/writer/init/create-readonly-user.sql: criapokedex_readere concede permissões de leitura.pokedex-postgres-reader: réplica física read-only do Writer via WAL. Depende depokedex-postgres-writersaudável.- Script executado no startup do Reader:
docker/postgres/reader/bootstrap-replica.sh: fazpg_basebackupdo Writer (quando a réplica ainda não existe) e inicia o PostgreSQL em modo réplica.pokedex-redis: broker/cache usado pelo Sidekiq e app Rails.rails: aplicação web Rails (porta3000). Depende de Writer, Reader e Redis saudáveis.- Script executado no startup do Rails:
bin/docker-entrypoint: ao subir com./bin/rails server, roda./bin/rails db:prepareantes de iniciar o servidor.sidekiq: processamento assíncrono de jobs. Depende de Writer, Reader e Redis saudáveis.- Observação Sidekiq: usa o mesmo
ENTRYPOINT(bin/docker-entrypoint), mas como o comando ébundle exec sidekiq, não disparadb:prepare. metabase-postgres: banco interno do Metabase (metadados/configurações).metabase: app de BI (porta3002). Depende demetabase-postgressaudável.
Fluxo de dependências (simplificado):
pokedex-postgres-writer->pokedex-postgres-readerpokedex-redis+pokedex-postgres-writer+pokedex-postgres-reader->railsesidekiqmetabase-postgres->metabase
- Docker
- Docker Compose
- Ruby/Bundler local (para rodar tasks Rails locais)
docker compose down -v --remove-orphansdocker compose up -d --builddocker compose ps- App Rails:
http://localhost:3000 - Sidekiq:
http://localhost:3000/sidekiq - Metabase:
http://localhost:3002
- Acesse
http://localhost:3002 - Finalize o onboarding (criação do usuário admin) com:
- E-mail:
[email protected] - Senha:
Gea#1234
No Metabase, crie uma nova conexão PostgreSQL com os dados abaixo:
- Display name:
Pokedex Reader - Host:
pokedex-postgres-reader - Port:
5432 - Database name:
pokedex_development - Username:
pokedex_reader - Password:
pokedex_reader
O que significa cada propriedade:
- Display name:
Pokedex Readeré só o nome amigável dentro do Metabase (pode ser outro, sem impacto técnico). - Host:
pokedex-postgres-readeré o nome do serviço da réplica nodocker-compose.yml; como o Metabase também está em container, ele enxerga esse host pela rede interna do Compose. - Port:
5432é a porta interna padrão do PostgreSQL no containerpokedex-postgres-reader(no host externo ela é publicada como5434, mas entre containers continua5432). - Database name:
pokedex_developmenté o banco definido no Compose (POSTGRES_DB) para Writer/Reader. - Username:
pokedex_readeré o usuário criado no bootstrap do Writer emdocker/postgres/writer/init/create-readonly-user.sql. - Password:
pokedex_readeré a senha definida na criação desse mesmo usuário no script de init.
Por que esse usuário é o ideal para BI:
- O script concede
CONNECT,USAGEno schemapubliceSELECTem tabelas/sequences. - Não concede permissão de escrita (
INSERT/UPDATE/DELETE), então o Metabase consulta sem risco de alterar dados.
Observação:
- No Metabase em container, use
pokedex-postgres-reader:5432(rede Docker). localhost:5434é para acesso direto da máquina host (ex.: pgAdmin), não para a conexão interna do Metabase.
Observação: o arquivo CSV de Pokémons está disponível em lib/csv.
Com a conexão criada, execute:
bin/rails metabase:seed_pokedexEla cria/atualiza:
- Collection
Pokedex - Dashboard
Pokedex Analytics - Perguntas:
- Top 10 Pokemons Mais Fortes (
name,total) - Top 10 Pokemons Mais Velozes (
name,speed) - Top 10 Pokemons com Maiores Ataques (
name,attack) - Quantidade de Pokemons por Tipo
- Top 10 Pokemons Mais Fortes (
Variáveis opcionais para customizar a conexão usada pela task:
METABASE_DB_NAME(default:Pokedex Reader)METABASE_DB_HOST(default:pokedex-postgres-reader)METABASE_DB_PORT(default:5432)METABASE_DB_DATABASE(default:pokedex_development)METABASE_DB_USER(default:pokedex_reader)METABASE_DB_PASSWORD(default:pokedex_reader)METABASE_DB_SSL(default:false)
Com a stack no ar (docker compose up -d), no pgAdmin crie 2 conexões em Register > Server.
- Name:
Pokedex Writer - Host:
localhost - Port:
5433 - Maintenance DB:
pokedex_development - Username:
postgres - Password:
postgres
- Name:
Pokedex Reader - Host:
localhost - Port:
5434 - Maintenance DB:
pokedex_development - Username:
pokedex_reader - Password:
pokedex_reader
No Query Tool de cada servidor:
select pg_is_in_recovery();false= primário (Writer)true= réplica (Reader)
No Reader:
show transaction_read_only;Esperado: on (somente leitura).
Roteiro sugerido: executar tudo no Query Tool do pgAdmin, abrindo cada servidor separado (Pokedex Writer e Pokedex Reader).
Nesta POC, Writer e Reader sao dois servidores PostgreSQL distintos (duas instâncias com armazenamento separado), conectados por replicação física WAL.
No Writer e no Reader, rode:
select current_database() as db, pg_is_in_recovery() as em_recovery;
select datname from pg_database order by datname;
select name, setting from pg_settings where name = 'data_directory';Interpretação:
pg_is_in_recovery = falseno Writer (primário).pg_is_in_recovery = trueno Reader (réplica).data_directorydeve apontar para diretórios diferentes entre as duas instâncias.
No Writer, conectado como postgres:
select
rolname,
rolcanlogin,
rolsuper,
rolreplication
from pg_roles
where rolname in ('postgres', 'replicator', 'pokedex_reader')
order by rolname;Leitura esperada:
postgres: superusuário, acesso total (admin).replicator:rolreplication = true, usado para streaming da réplica.pokedex_reader: login de leitura (usado por consultas/BI).
Ainda no Writer, validar permissões do pokedex_reader:
select
grantee,
table_schema,
table_name,
privilege_type
from information_schema.role_table_grants
where grantee = 'pokedex_reader'
and table_schema = 'public'
order by table_name, privilege_type;No Writer (postgres), gravar:
insert into pokemons (
pokedex_number, name, type_1, total, hp, attack, defense, sp_atk, sp_def, speed,
generation, legendary, created_at, updated_at
) values (
9999, 'Replica Test', 'Normal', 1, 1, 1, 1, 1, 1, 1,
1, false, now(), now()
);No Reader (pokedex_reader), consultar:
select pokedex_number, name, total
from pokemons
where pokedex_number = 9999;No Reader (pokedex_reader), tentar gravar:
insert into pokemons (
pokedex_number, name, type_1, total, hp, attack, defense, sp_atk, sp_def, speed,
generation, legendary, created_at, updated_at
) values (
10000, 'Nao Deve Gravar', 'Normal', 1, 1, 1, 1, 1, 1, 1,
1, false, now(), now()
);Esperado: erro de escrita (replica/read-only e sem privilegio de escrita para pokedex_reader).
No Writer (postgres), ver se a réplica está conectada:
select
application_name,
client_addr,
state,
sync_state,
sent_lsn,
write_lsn,
flush_lsn,
replay_lsn,
pg_size_pretty(pg_wal_lsn_diff(sent_lsn, replay_lsn)) as lag_bytes_aprox
from pg_stat_replication;Como ler:
state = streamingindica replicação ativa.lag_bytes_aproxperto de0 bytesindica Reader acompanhando o Writer.
No Reader (postgres), ver receptor WAL:
select
status,
receive_start_lsn,
written_lsn,
flushed_lsn,
latest_end_lsn,
latest_end_time
from pg_stat_wal_receiver;No Reader (postgres ou pokedex_reader), medir atraso em tempo:
select
now() as agora_reader,
pg_last_xact_replay_timestamp() as ultima_transacao_replicada_em,
now() - pg_last_xact_replay_timestamp() as atraso_aprox
;Observações:
- Se
atraso_aproxfor pequeno (milissegundos/segundos), replicação está saudável. - Se vier
NULL, pode nao ter transação recente para medir (gere uma escrita nova no Writer e consulte de novo).
Quando a réplica está sendo recriada pela primeira vez, no Writer (postgres) use:
select
pid,
phase,
backup_total,
backup_streamed,
tablespaces_total,
tablespaces_streamed
from pg_stat_progress_basebackup;Essa visão mostra o progresso do pg_basebackup enquanto a réplica está em bootstrap.