Você está na página 1de 483

Sumário

1 A Apostila 11
1.1 Notações utilizadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.1.1 Comandos e saída em tela . . . . . . . . . . . . . . . . . . . . . . . . 12
1.1.2 Aviso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.1.3 Observação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.1.4 Conteúdo extra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

2 Introdução ao PostgreSQL 14
2.1 Sobre o PostgreSQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.1.1 O que é o PostgreSQL? . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.1.2 Extensibilidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.1.3 Licença . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.1.4 Como se Fala e Como se Escreve? . . . . . . . . . . . . . . . . . . . . 15
2.1.5 PGDG - PostgreSQL Global Development Group . . . . . . . . . . . . 15
2.2 Suporte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.2.1 Sobre Software Livre em Geral . . . . . . . . . . . . . . . . . . . . . . 16
2.2.2 Suporte da comunidade . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.2.3 Suporte comercial . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.3 Versionamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.3.1 Política de Versionamento . . . . . . . . . . . . . . . . . . . . . . . . 18
2.3.2 Política de ciclo de vida das versões do PostgreSQL . . . . . . . . . . 18
2.4 Revisão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

3 Instalação do PostgreSQL 20
3.1 Sobre instalação do PostgreSQL . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.2 Instalação via pacotes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.2.1 Pacotes PGDG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.2.2 Instalação via pacotes - distribuições Linux derivadas de Red Hat . . . 24
3.3 Instalação via código-fonte . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.3.1 Preparativos Debian . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.3.2 Preparativos RedHat . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

2
3 Sumário

3.3.3 Instalação via código-fonte . . . . . . . . . . . . . . . . . . . . . . . 29


3.4 SSH sem senha . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.5 Revisão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

4 Noções básicas 39
4.1 Conectando à uma base de dados com o psql . . . . . . . . . . . . . . . . . . 40
4.1.1 Variáveis de ambiente de conexão . . . . . . . . . . . . . . . . . . . . 46
4.2 Bases de dados no PostgreSQL . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.2.1 Bancos de dados padrão e templates . . . . . . . . . . . . . . . . . . 47
4.2.2 Templates de bancos de dados . . . . . . . . . . . . . . . . . . . . . 47
4.2.3 Propriedades de bancos de dados . . . . . . . . . . . . . . . . . . . . 57
4.3 Tablespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.3.1 Movendo tablespaces . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.4 Schemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.4.1 search_path . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.5 Revisao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69

5 Autenticação e autorização 70
5.1 Roles: Gerenciamento de usuários no Postgres . . . . . . . . . . . . . . . . . 71
5.1.1 Prompt psql e roles . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
5.1.2 Permissões de papéis . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
5.1.3 Gerenciando papéis e grupos de usuários . . . . . . . . . . . . . . . . 72
5.1.4 Transferindo propriedades: REASSIGN OWNED . . . . . . . . . . . . 83
5.2 Gerenciamento de permissões . . . . . . . . . . . . . . . . . . . . . . . . . . 85
5.2.1 Privilégios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
5.2.2 Tipos de privilégios . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
5.2.3 Gerenciando privilégios . . . . . . . . . . . . . . . . . . . . . . . . . . 88
5.2.4 Permissões em tabelas . . . . . . . . . . . . . . . . . . . . . . . . . . 88
5.2.5 Permissões em colunas . . . . . . . . . . . . . . . . . . . . . . . . . . 96
5.2.6 Permissões em bancos de dados . . . . . . . . . . . . . . . . . . . . . 97
5.2.7 Permissões em schemas . . . . . . . . . . . . . . . . . . . . . . . . . 101
5.2.8 Privilégios padrão . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
5.3 Autenticação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.3.1 pg_hba.conf - host-based authentication (autenticação baseada em
máquina) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.3.2 Campos do pg_hba.conf . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.3.3 O arquivo .pgpass . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
5.3.4 Arquivo de serviço de conexão . . . . . . . . . . . . . . . . . . . . . . 116
5.4 RLS: Row Level Security - Segurança em nível de linha . . . . . . . . . . . . . 117

6 Instâncias PostgreSQL 128


6.1 Gerenciamento de instâncias . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
4 Sumário

6.1.1 Conceito de instância / cluster PostgreSQL . . . . . . . . . . . . . . . 129


6.1.2 initdb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
6.1.3 systemd & systemctl . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
6.1.4 Utilizando o pg_ctl . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
6.1.5 Modos de parada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
6.2 Arquivos Físicos e OID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
6.2.1 OIDs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
6.2.2 Layout de arquivos do PGDATA . . . . . . . . . . . . . . . . . . . . . 138
6.2.3 Armazenamento físico . . . . . . . . . . . . . . . . . . . . . . . . . . 139
6.2.4 TOAST (The Oversized-Attribute Storage Technique) . . . . . . . . . 140
6.3 Revisao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145

7 Configuração 146
7.1 postgresql.conf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
7.1.1 Tipos de valores do postgresql.conf . . . . . . . . . . . . . . . . . . . 147
7.1.2 Directivas include . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
7.1.3 Contextos de parâmetros de configuração . . . . . . . . . . . . . . . . 148
7.1.4 Exibir / alterar parâmetros . . . . . . . . . . . . . . . . . . . . . . . . 149
7.2 Formas alternativas de configurar parâmetros . . . . . . . . . . . . . . . . . . 150
7.3 A view pg_settings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
7.4 O comando ALTER SYSTEM . . . . . . . . . . . . . . . . . . . . . . . . . . 154

8 Arquitetura e funcionamento interno do PostgreSQL 156


8.1 Fundamentos da arquitetura . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
8.1.1 libpq . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
8.1.2 Blocos / páginas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
8.1.3 Como uma conexão é estabelecida? . . . . . . . . . . . . . . . . . . . 158
8.1.4 Fluxo de dados em consultas . . . . . . . . . . . . . . . . . . . . . . 159
8.1.5 Fluxo de dados em escritas . . . . . . . . . . . . . . . . . . . . . . . 160
8.2 Os processos do PostgreSQL . . . . . . . . . . . . . . . . . . . . . . . . . . 162
8.2.1 postgres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
8.2.2 archiver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
8.2.3 autovacuum launcher . . . . . . . . . . . . . . . . . . . . . . . . . . 162
8.2.4 autovacuum worker . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
8.2.5 background writer / bgwriter . . . . . . . . . . . . . . . . . . . . . . 163
8.2.6 checkpointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
8.2.7 stats collector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
8.2.8 logger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
8.2.9 walwriter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
8.3 Shared buffers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
8.4 WAL (Write Ahead Log): integridade de dados . . . . . . . . . . . . . . . . . 167
5 Sumário

8.4.1 Parâmetros WAL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167


8.5 Checkpoint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
8.5.1 Sobre Checkpoint . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
8.5.2 Parâmetros checkpoint . . . . . . . . . . . . . . . . . . . . . . . . . . 169
8.5.3 Garantia de gravação em armazenamento . . . . . . . . . . . . . . . . 171
8.6 Revisao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175

9 MVCC: Multi Version Concurrency Control 176


9.1 Sobre MVCC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
9.1.1 Identificação de transações (xid), colunas de sistema xmin e xmax . . . 177
9.2 Travas (Locks) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
9.2.1 Deadlocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180

10 Rotinas de manutenção 187


10.1 Vacuum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
10.1.1 Princípios básicos de vacumização . . . . . . . . . . . . . . . . . . . . 188
10.1.2 Mapa de visibilidade . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
10.1.3 Atualizando o mapa de visibilidade . . . . . . . . . . . . . . . . . . . 191
10.1.4 Prevenindo falhas de id de transações envolventes (transaction id wraparound)191
10.1.5 Freeze . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
10.1.6 O comando VACUUM . . . . . . . . . . . . . . . . . . . . . . . . . . 193
10.2 Analyze . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
10.2.1 Atualizando o planejador de estatísticas . . . . . . . . . . . . . . . . . 200
10.3 Autovacuum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
10.3.1 Ponto de partida de vacuum (vacuum threshold) . . . . . . . . . . . . 206
10.4 Revisao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211

11 Performance 212
11.1 Performance / Tuning do PostgreSQL . . . . . . . . . . . . . . . . . . . . . . 213
11.2 Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
11.2.1 Disco / armazenamento . . . . . . . . . . . . . . . . . . . . . . . . . 215
11.2.2 Memória RAM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
11.2.3 CPU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
11.2.4 Rede . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
11.3 Sistema Operacional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
11.3.1 Kernel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
11.3.2 Comunicação inter-processos . . . . . . . . . . . . . . . . . . . . . . 225
11.3.3 Swapiness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
11.3.4 Memory overcommit . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
11.3.5 Limpeza do page cache . . . . . . . . . . . . . . . . . . . . . . . . . 229
11.3.6 Sistema de arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
11.3.7 UUID no fstab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
6 Sumário

11.3.8 Coisas a serem evitadas em servidores de banco de dados . . . . . . . 235


11.4 Aplicação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
11.4.1 Modelagem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
11.4.2 Indexação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
11.4.3 ORMs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
11.5 Otimização de Consultas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
11.5.1 Testar consultas para otimizar . . . . . . . . . . . . . . . . . . . . . . 237
11.5.2 Dicas SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
11.6 Pool de conexões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
11.6.1 PgPool II . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
11.6.2 PgBouncer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
11.7 OLTP (OnLine Transaction Processing) . . . . . . . . . . . . . . . . . . . . . 248
11.7.1 Características . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
11.7.2 Parametrizações para OLTP . . . . . . . . . . . . . . . . . . . . . . . 248
11.8 OLAP (OnLine Analytical Processing) . . . . . . . . . . . . . . . . . . . . . . 250
11.8.1 Algumas características próprias . . . . . . . . . . . . . . . . . . . . . 250
11.8.2 Parametrizações no PostgreSQL . . . . . . . . . . . . . . . . . . . . . 250
11.9 pgbench e benchmarks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
11.9.1 Melhores práticas com pgbench . . . . . . . . . . . . . . . . . . . . . 251
11.10Visões materializadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
11.10.1 Quando utilizar visões materializadas? . . . . . . . . . . . . . . . . . . 253
11.11Tabelas não logadas - Unlogged tables . . . . . . . . . . . . . . . . . . . . . 257
11.11.1 Qual a utilidade de uma tabela não logada? . . . . . . . . . . . . . . 257
11.12Fillfactor - Fator de preenchimento . . . . . . . . . . . . . . . . . . . . . . . 261
11.12.1 Fillfactor em tabelas . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
11.12.2 Fillfactor em índices . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
11.13Plano de execução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
11.14O caminho de uma consulta . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
11.14.1 Conexão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
11.14.2 Parser (analisador) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
11.14.3 Rewrite system (sistema de reescrita) . . . . . . . . . . . . . . . . . . 268
11.14.4 Planejador / otimizador de consultas . . . . . . . . . . . . . . . . . . 268
11.14.5 Executor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
11.14.6 O comando EXPLAIN . . . . . . . . . . . . . . . . . . . . . . . . . . 269
11.14.7 Parêmetros do planejador de consultas . . . . . . . . . . . . . . . . . 272
11.14.8 Métodos de busca . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
11.14.9 Paralelização de execução . . . . . . . . . . . . . . . . . . . . . . . . 273
11.14.10Otimizador de busca de algoritmo genético . . . . . . . . . . . . . . . 275
11.15Huge pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
11.15.1 Transparent Huge Pages (THP) . . . . . . . . . . . . . . . . . . . . . 277
7 Sumário

12 Observabilidade 283
12.1 Observabilidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
12.1.1 Métricas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
12.1.2 Rastreamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
12.1.3 Logs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
12.1.4 Monitoramento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
12.2 Logs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
12.2.1 Configurações de logs no PostgreSQL . . . . . . . . . . . . . . . . . . 285
12.3 Análise de logs com pgbadger . . . . . . . . . . . . . . . . . . . . . . . . . . 290
12.4 Monitoramento pontual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
12.5 Monitoramento contínuo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
12.5.1 SAR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
12.6 Catálogos e views de sistema . . . . . . . . . . . . . . . . . . . . . . . . . . 305
12.6.1 Catálogos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305
12.6.2 Views de sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305
12.6.3 Information Schema . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
12.7 Colunas de sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309
12.8 O Módulo pg_stat_statements . . . . . . . . . . . . . . . . . . . . . . . . . 312
12.8.1 Configurações do pg_stat_statements (nome - tipo - contexto) . . . . 312

13 Particionamento de tabelas 315


13.1 O que é particionamento de tabelas . . . . . . . . . . . . . . . . . . . . . . . 316
13.1.1 Benefícios do particionamento de tabelas . . . . . . . . . . . . . . . . 316
13.1.2 Quando particionar uma tabela? . . . . . . . . . . . . . . . . . . . . . 317
13.1.3 Partição padrão (default) . . . . . . . . . . . . . . . . . . . . . . . . 317
13.1.4 Partition pruning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
13.1.5 Dicas e boas práticas de particionamento . . . . . . . . . . . . . . . . 318
13.2 Particionamento por faixa de valores (RANGE) . . . . . . . . . . . . . . . . . 319
13.3 Particionamento por lista enumerada (LIST) . . . . . . . . . . . . . . . . . . 325
13.4 Particionamento por hash . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
13.5 Particionamento multinível . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331

14 Backup e Restauração 335


14.1 Sobre Backup e Restauração . . . . . . . . . . . . . . . . . . . . . . . . . . . 336
14.1.1 Backup físico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336
14.1.2 Backup full (completo) . . . . . . . . . . . . . . . . . . . . . . . . . 336
14.1.3 Backup incremental . . . . . . . . . . . . . . . . . . . . . . . . . . . 336
14.1.4 Backup lógico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336
14.1.5 RPO - Recover Point Objective; Ponto Objetivo de Recuperação . . . 337
14.1.6 RTO - Recover Time Objective; Objetivo de Tempo de Recuperação . 337
14.2 Preparação do ambiente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
8 Sumário

14.2.1 Diretórios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339


14.3 Backup físico off-line: snapshot . . . . . . . . . . . . . . . . . . . . . . . . . 341
14.4 Backup Físico Online . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343
14.4.1 Arquivamento contínuo . . . . . . . . . . . . . . . . . . . . . . . . . 343
14.5 pg_basebackup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
14.6 Dump . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352
14.6.1 pg_dump: dump de bases de dados individuais . . . . . . . . . . . . . 352
14.6.2 pg_restore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
14.6.3 pg_dumpall . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
14.7 PITR - Point In Time Recovery . . . . . . . . . . . . . . . . . . . . . . . . . 364

15 Replicação 370
15.1 O que é replicação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
15.1.1 Conceitos de replicação . . . . . . . . . . . . . . . . . . . . . . . . . 371
15.1.2 Slots de replicação . . . . . . . . . . . . . . . . . . . . . . . . . . . . 373
15.2 Replicação física via streaming . . . . . . . . . . . . . . . . . . . . . . . . . . 374
15.2.1 Preparação do laboratório de replicação streaming . . . . . . . . . . . 375
15.2.2 Configurações relativas à replicação streaming . . . . . . . . . . . . . 375
15.2.3 Funções relativas à replicação streaming . . . . . . . . . . . . . . . . 377
15.2.4 Procedimentos para replicação via streaming assíncrona . . . . . . . . 379
15.2.5 Monitorando a replicação via streaming . . . . . . . . . . . . . . . . . 383
15.2.6 Mudando o ambiente para replicação síncrona . . . . . . . . . . . . . 387
15.2.7 Failover . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
15.2.8 Failback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
15.3 Replicação lógica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392
15.3.1 Publicação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
15.3.2 Subscrição e gerenciamento de slots de replicação . . . . . . . . . . . 393
15.3.3 Conflitos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394
15.3.4 Restrições . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395
15.3.5 Arquitetura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
15.3.6 Monitoramento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
15.3.7 Configuração do publicador . . . . . . . . . . . . . . . . . . . . . . . 396
15.4 Configuração do assinante . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
15.4.1 Preparação do laboratório . . . . . . . . . . . . . . . . . . . . . . . . 397
15.4.2 Procedimentos e testes para replicação lógica . . . . . . . . . . . . . . 397
15.5 Soluções de terceiros para alta disponibilidade no PostgreSQL . . . . . . . . . 401
15.5.1 repmgr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401
15.5.2 Patroni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401
15.5.3 PAF - PostgreSQL Automatic Failover . . . . . . . . . . . . . . . . . 401
15.5.4 CitusDB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401
15.5.5 Greenplum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402
9 Sumário

15.5.6 YugabyteDB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402


15.6 Replicação e alta disponibilidade com repmgr . . . . . . . . . . . . . . . . . . 403
15.6.1 Witness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403
15.6.2 Ambiente de laboratório . . . . . . . . . . . . . . . . . . . . . . . . . 403
15.6.3 Instalação do repmgr via compilação do código-fonte . . . . . . . . . . 404
15.6.4 Configurações de nós . . . . . . . . . . . . . . . . . . . . . . . . . . 408
15.6.5 SSH sem senha . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411
15.6.6 Nó primário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
15.6.7 Nós standbys . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413
15.6.8 Nó witness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414
15.6.9 Failover . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416
15.6.10 Failback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417
15.6.11 Switchover . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418

16 Anexo 1 - Índices 419


16.1 Sobre Índice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
16.1.1 Tipos de Índices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
16.1.2 Dicas Gerais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
16.2 Índices B-tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422
16.3 Índices Hash . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424
16.4 Índices GiST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426
16.5 Índices SP-GiST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429
16.6 Índices GIN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431
16.7 Índices BRIN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 433
16.7.1 Páginas por Faixa . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435
16.8 Índices Compostos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 437
16.9 Índices Parciais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 439
16.9.1 Conclusão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 442
16.10Índices de cobertura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443
16.11Reconstrução de índices: REINDEX . . . . . . . . . . . . . . . . . . . . . . . 447
16.12CLUSTER – Índices clusterizados . . . . . . . . . . . . . . . . . . . . . . . . 449
16.13Exclusão de índices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452

17 Anexo 2 - Scripts de conveniência 454


17.1 O que são scripts de conveniência? . . . . . . . . . . . . . . . . . . . . . . . 455
17.1.1 Scripts de conveniência Debian . . . . . . . . . . . . . . . . . . . . . 455
17.1.2 Scripts de conveniência RedHat . . . . . . . . . . . . . . . . . . . . . 458

18 Anexo 3 - Estratégias de atualização 459


18.1 Sobre estratégias de atualização . . . . . . . . . . . . . . . . . . . . . . . . . 460
18.1.1 Atualização via dump . . . . . . . . . . . . . . . . . . . . . . . . . . 460
18.1.2 Atualização via replicação lógica . . . . . . . . . . . . . . . . . . . . . 460
10 Sumário

18.1.3 pg_upgrade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 460


18.1.4 Parâmetros e variáveis de ambiente do pg_upgrade . . . . . . . . . . . 461
18.1.5 Sobre o laboratório . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461

19 Anexo 4 - FDW: Foreign-Data Wrappers 468


19.1 Sobre Foreign-Data Wrappers . . . . . . . . . . . . . . . . . . . . . . . . . . 469
19.2 postgres_fdw: acessando outro servidor PostgreSQL . . . . . . . . . . . . . . 470
19.2.1 Preparativos no servidor que vai ser acessado (srv1) . . . . . . . . . . 470
19.2.2 Preparativos no servidor com FDW (srv0) . . . . . . . . . . . . . . . . 472
19.2.3 Testes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 474
19.3 mysql_fdw: Acessando o MySQL ou MariaDB . . . . . . . . . . . . . . . . . 476
19.3.1 Preparativos no servidor MariaDB / MySQL (srv1) . . . . . . . . . . . 476
19.3.2 Instalação do mysql_fdw no servidor PostgreSQL (srv0) . . . . . . . . 477
19.3.3 Configuração do mysql_fdw e testes . . . . . . . . . . . . . . . . . . 479
19.4 file_fdw: Acesso a arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . 482
1
A Apostila
• Sobre esta apostila
• Notações utilizadas

11
12 1. A Apostila

Notações utilizadas
Comandos e saída em tela

Aqui, vamos fazer alguns exemplos de comandos em diferentes contextos.

Simbologia Descrição
[#] Shell no sistema operacional como usuário root
[$] Shell no sistema operacional como usuário não-root
[>] Shell interativo (banco de dados, programação)

[>] Shell interativo SQL e sua respectiva saída:

SELECT
col1,
col2
FROM tabela
WHERE col1 != 1
AND col2 = 0
ORDER BY col1;

col1 | col2
------+------
5 | 0

[$] Linha de comando usuário comum:

pandoc -s -o teste.pdf metadata.yaml foo.md bar.md

[#] Linha de comando como root:

apt update
13 1. A Apostila

Aviso

Precauções a serem tomadas para evitar problemas.

Aviso

O mal uso deste comando pode trazer consequências indesejáveis.

Observação

Observações e dicas sobre o assunto tratado.

Observação

O comando anterior não necessita especificar a porta se for usada a padrão (5432).

Conteúdo extra

Devido à questão do tempo, infelizmente não é possível passar todo conteúdo da apostila.
Dentro do conteúdo normal, a seguinte demarcação é utilizada:

——————– EXTRA ——————–


2
Introdução ao PostgreSQL
• Sobre o PostgreSQL
• História do PostgreSQL
• Suporte
• Versionamento

14
15 2. Introdução ao PostgreSQL

Sobre o PostgreSQL
O que é o PostgreSQL?

PostgreSQL é um sistema gerenciador de bancos de dados multi-paradigma, predominante-


mente objeto-relacional, porém com características NoSQL.
Seu código-fonte é aberto, majoritariamente escrito em linguagem C.
Sua história com mais detalhes pode ser lida na documentação oficial:

https://www.postgresql.org/docs/current/history.html

Extensibilidade

O PostgreSQL pode ter suas funcionalidades ampliadas instalando extensões que podem adi-
cionar tipos de dados, funções, linguagens procedurais, foreign data wrappers etc.

Licença

Devido a sua licença [1] liberal, baseada na licença BSD [2], o PostgreSQL pode ser usado,
modificado e distribuído por qualquer um gratuitamente sob qualquer pretexto, seja privado,
comercial ou acadêmico.

[1] https://opensource.org/licenses/postgresql
[2] https://opensource.org/licenses/BSD-3-Clause

Como se Fala e Como se Escreve?

Uma dúvida comum ao PostgreSQL é seu nome.


As formas corretas são as seguintes:

• Postgres: pronuncia-se “postígres” (sim, o “s” é pronunciado!);

• PostgreSQL: pronuncia-se “postgres és quiu el”.

Seu nome é uma referência ao Ingres, que foi a base de seu código-fonte no início.

PGDG - PostgreSQL Global Development Group

É uma entidade, ou seja, um grupo de desenvolvedores que trabalha nos códigos e mantém o
PostgreSQL.
16 2. Introdução ao PostgreSQL

Suporte
Informações gerais podem ser encontradas no link:
https://www.postgresql.org/support/

Sobre Software Livre em Geral

Infelizmente, ainda hoje existe um mito a respeito de qualquer software livre.


É o mito expresso pelas perguntas: “se der algum problema, quem se responsabiliza? Se eu
descobrir um bug, será rapidamente corrigido?”.
Em softwares proprietários, cujo código-fonte é fechado, quem dá suporte é a empresa desen-
volvedora. Isso passa uma ideia (falsa) de segurança para quem adquire uma solução propri-
etária. Há casos (não raros) de softwares proprietários que tem bug descoberto e divulgado por
usuários há anos sem ter sido corrigido. Isso mostra a outra face de uma pseudo-segurança,
pois o código-fonte está nas mãos de um círculo limitado de pessoas. Sem mencionar também
que, devido à solução proprietária ser fechada, não se sabe o que roda além do que é citado
para quem o adquire.
Uma solução em Software Livre prima por liberdade, privacidade e segurança.
Há muitas empresas que ajudam (financeiramente ou fornecendo mão de obra) e que veem
claramente vantagens para si em colaborar com um software mantido pela Comunidade de
Software Livre.
Sobre bugs descobertos em uma aplicação de código-fonte aberto, assim que descobertos, são
reportados à Comunidade que geralmente soluciona em pouquíssimo tempo.
Quanto à questão de suporte a um software de código-fonte, temos as opções de buscar ajuda
na Comunidade ou contratando uma empresa de consultoria.

Suporte da comunidade

• Sites oficiais
– Global: https://www.postgresql.org/
– Wiki: https://wiki.postgresql.org/

• Documentação
A documentação do PostgreSQL é muito rica e tem as opções:
– Arquivo PDF: https://www.postgresql.org/docs/manuals/
– On-line: https://www.postgresql.org/docs/.

• Listas de discussão
Uma lista de discussão é um meio rápido de resolução de problemas. Contando com
uma comunidade forte e vibrante, temos as listas:
– Internacional: https://lists.postgresql.org
17 2. Introdução ao PostgreSQL

• Blogs
São vários os blogs ao redor do planeta sobre PostgreSQL, mas, para facilitar, temos
blogs oficiais da comunidade que aglutinam posts de outros blogs, de forma a termos
um conhecimento diversificado por meio de tutoriais e artigos divulgados nesses.
– Planet PostgreSQL: http://planet.postgresql.org/

Suporte comercial

Há várias empresas que provêm suporte ao PostgreSQL no mundo inteiro. Elas podem ser
encontradas por região [1] ou especificamente por hosting [2]:

• [1] https://www.postgresql.org/support/professional_support/
• [2] https://www.postgresql.org/support/professional_hosting/
18 2. Introdução ao PostgreSQL

Versionamento
Política de Versionamento

Até a versão 9.6, o PostgreSQL adotava o modelo de versão X.Y.Z, sendo que a parte X.Y
era a versão majoritária e a Z a versão minoritária. A partir da versão 10, adotou-se o modelo
X.Y, sendo X a versão majoritária e Y a versão minoritária. É fortemente recomendado a
atualização para a última versão minoritária (minor version: X.Y) para qualquer que seja sua
sua versão majoritária (major release: X) em uso.
As versões majoritárias do PostgreSQL incluem novas funcionalidades e ocorrem uma vez por
ano. Estas Major releases normalmente mudam o formato interno do sistema de tabelas e
arquivos de dados, de forma que o dump ou o uso do módulo pg_upgrade são necessários para
a atualização. Versões minoritárias (minor releases) são numeradas incrementando a segunda
parte do número da versão, e.g. 10.0 para 10.1. Nestas versões, apenas correções de bugs
são aplicadas. É recomendado que todos usuários atualizem para a versão de lançamento
minoritária (X.Y) assim que possível. Versões minoritárias corrigem bugs, sejam eles de segu-
rança ou até mesmo risco de perda de dados. É importante verificar periodicamente no site
oficial do PostgreSQL a respeito dessas correções para evitar danos e prejuízos.
A comunidade considera que não atualizar é mais arriscado do que atualizar.
Atualizando para um minor release não requer um dump e restore; simplesmente pare o banco
de dados, instale os binários atualizados e reinicie o servidor.
Para alguns lançamentos, mudanças manuais podem ser necessárias para completar a atual-
ização, então sempre leia as notas de lançamento antes de atualizar.

Política de ciclo de vida das versões do PostgreSQL

O projeto PostgreSQL tem como política suportar uma versão majoritária por 5 anos.
Bugs e/ou falhas de segurança encontradas após este período não serão mais corrigidos.

Para saber sobre versões suportadas no momento, há uma tabela no site oficial do PostgreSQL:
http://www.postgresql.org/support/versioning/

No site oficial do PostgreSQL há uma parte que tem uma matriz de funcionalidades que foram
adicionadas ao longo das versões:
http://www.postgresql.org/about/featurematrix/
19 2. Introdução ao PostgreSQL

Revisão
1. P: Com que frequência são lançadas novas versões majoritárias (major) do PostgreSQL?
R:
2. P: Com que frequência são lançadas novas versões minoritárias (minor) do PostgreSQL?
R:
3. P: O suporte da comunidade se estende a quantas versões do PostgreSQL?
R:
3
Instalação do PostgreSQL
• Sobre a instalação do PostgreSQL
• Instalação via pacotes
• Instalação via código-fonte
• SSH sem senha

20
21 3. Instalação do PostgreSQL

Sobre instalação do PostgreSQL


A escolha de onde e como instalar o PostgreSQL são fatores importantes para o futuro servidor
de banco de dados.
O PGDG disponibiliza pacotes atualizados para instalação no seguinte link:

https://www.postgresql.org/download

Muitas vezes, os pacotes disponíveis para instalação em uma distribuição Linux não incluem a
versão mais recente do PostgreSQL, então configurar o repositório oficial pode ser muito útil.
No entanto, há casos também que se opta por instalar o PostgreSQL via compilação de seu
código-fonte, que oferece mais flexibilidade, porém é muito mais demorada e complexa.
22 3. Instalação do PostgreSQL

Instalação via pacotes


É a forma mais simples de instalação.
Uma instalação via pacotes se dá através de um sistema de gerenciamento de pacotes, tais
como o apt (Debian/Ubuntu), yum (RedHat/CentOS) ou pkgng (FreeBSD).
O sistema gerenciador de pacotes baixa o(s) pacote(s) do(s) binário(s) pré compilados e resolve
qualquer dependência que existir.
Pacotes são binários compilados de uma forma geral para poder rodar em qualquer máquina
e não têm otimizações específicas para um determinado hardware.
Outra coisa a se considerar é o suporte da distribuição Linux escolhida, pois podem haver
restrições com relação a isso de forma a obrigar o usuário a instalar somente os pacotes que
a distribuição disponibiliza.
Ao atualizar o PostgreSQL de uma versão minoritária para outra, é preciso reiniciar o serviço
após instalar o pacote.

Pacotes PGDG

São pacotes oficiais disponibilizados pelo próprio PGDG, que torna possível instalar a última
versão do PostgreSQL. Para isso, é preciso configurar o repositório de acordo com a distribuição
ou família de distribuição Linux:
- RedHat - Debian - Ubuntu - SuSE

Além do próprio PostgreSQLm também são disponibilizados pacotes de ferramentas e exten-


sões. ### Instalação via pacotes - distribuições Linux derivadas de Debian

A instalação do PostgreSQL em distribuições baseadas em Debian é mais simples comparada


à instalação em distribuições baseadas em Red Hat, pois ela já cria uma instância de forma
automática. No entanto, esse comportamento costuma dificultar processos de automação.

Arquitetura de diretórios

Tipo Localização
Instalação /usr/lib/postgresql/<VERSÃO MAJORITÁRIA>
Configuração /etc/postgresql/<VERSÃO MAJORITÁRIA>/main
Dados /var/lib/postgresql/<VERSÃO MAJORITÁRIA>/main
Binários /usr/lib/postgresql/<VERSÃO MAJORITÁRIA>/bin
23 3. Instalação do PostgreSQL

[#] Criação do arquivo de configuração do repositório PGDG:

echo \
"deb http://apt.postgresql.org/pub/repos/apt `lsb_release -cs`-pgdg main" \
> /etc/apt/sources.list.d/pgdg.list

[#] Importar a chave de assinatura do repositório:

wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | \


apt-key add -

[#] Atualizar a lista de pacotes disponíveis do apt:

apt update

[#] Instalar a última versão do PostgreSQL.


Em caso de querer instalar uma versão específica utilizar ‘postgresql-<VERSÃO>’:

apt install -y postgresql

[#] De usuário root para usuário postgres:

su - postgres

[$] Testando:

psql -Atqc 'SELECT version()' | \


awk '{print $1" "$2}' # Exibindo a versão do PostgreSQL

PostgreSQL 13.1
24 3. Instalação do PostgreSQL

Instalação via pacotes - distribuições Linux derivadas de Red Hat

Arquitetura de diretórios

Tipo Localização
Instalação /usr/pgsql-<VERSÃO MAJORITÁRIA>
Configuração /var/lib/pgsql/<VERSÃO MAJORITÁRIA>/data
Dados /var/lib/pgsql/<VERSÃO MAJORITÁRIA>/data
Binários /usr/pgsql-<VERSÃO MAJORITÁRIA>/bin

[#] Instalação de pacotes necessários:

yum install -y redhat-lsb-core curl dnf

[#] Desabilite o módulo PostgreSQL built-in:

dnf -qy module disable postgresql

[#] Crie uma variável de ambiente que captura a versão majoritária da distro:

DISTRO_VERSION=`lsb_release -r | awk '{print $2}' | cut -f1 -d.`

[#] Variável de ambiente para a URL de download do pacote de configuração de repositório:

URL="https://download.postgresql.org/pub/repos/yum/reporpms/EL-\
${DISTRO_VERSION}-x86_64/pgdg-redhat-repo-latest.noarch.rpm"

[#] Instalação do pacote de configuração do repositório PGDG:

dnf install -y ${URL}


25 3. Instalação do PostgreSQL

[#] Atualize os repositórios de pacotes:

dnf check-update -y

[#] Digite a versão majoritária do PostgreSQL que deseja instalar:

read -p 'Versão majoritária do PostgreSQL: ' PGMAJOR

[#] Instalação do PostgreSQL e limpeza dos pacotes baixados:

dnf install -y postgresql${PGMAJOR}-server && dnf clean all

[#] Criação dos dados iniciais (cluster) do PostgreSQL:

/usr/pgsql-${PGMAJOR}/bin/postgresql-${PGMAJOR}-setup initdb

[#] Habilite e inicie o serviço do Postgres:

systemctl enable --now postgresql-${PGMAJOR}


26 3. Instalação do PostgreSQL

Instalação via código-fonte


É o tipo de instalação mais complicada e demorada a se fazer, porém mais flexível.
No processo de compilação podem ser especificadas opções de acordo com o que o usuário
precisa, além, é claro, de poder alterar o código fonte.
O PostgreSQL instalado por compilação pode ter um desempenho superior a um pacote binário
se tiver otimizações para a máquina na qual será instalado.
Existem diversas formas de fazer esse tipo de instalação.

Arquitetura de diretórios sugerida

Tipo Localização
Instalação /usr/local/pgsql/<VERSÃO MAJORITÁRIA>
Configuração /var/local/pgsql/<VERSÃO MAJORITÁRIA>/data
Dados (PGDATA) /var/local/pgsql/<VERSÃO MAJORITÁRIA>/data
Binários /usr/local/pgsql/<VERSÃO MAJORITÁRIA>/bin

[#] Variáveis de ambiente de pacotes comuns:

# Pacotes comuns a serem removidos após a instalação


PKG_RM='bison gcc flex make'

# Pacotes comuns a serem mantidos:


PKG='bzip2 wget'
27 3. Instalação do PostgreSQL

Preparativos Debian

[#] Variável de ambiente para pacotes Debian:

PKG_DEB="libreadline-dev libssl-dev libxml2-dev libldap2-dev \


uuid-dev python3-dev"

[#] Instalação de pacotes e posterior limpeza dos pacotes baixados:

apt update && apt install -y ${PKG} ${PKG_RM} ${PKG_DEB} && \


apt clean

[#] Habilitar configurações de localidades (locales) pt_BR.utf8 e en_US.utf8:

sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen


sed -i -e 's/# pt_BR.UTF-8 UTF-8/pt_BR.UTF-8 UTF-8/' /etc/locale.gen
28 3. Instalação do PostgreSQL

Preparativos RedHat

[#] Variável de ambiente para pacotes RedHat:

PKG_RH="readline-devel openssl-devel libxml2-devel openldap-devel \


libuuid-devel python3 python3-devel"

[#] Instalação de pacotes e posterior limpeza dos pacotes baixados:

dnf install -y ${PKG} ${PKG_RM} ${PKG_RH}

[#] Instalar locale para pt_BR.utf8 e posterior limpeza de pacotes baixados:

dnf install -y glibc-langpack-pt && dnf clean all


29 3. Instalação do PostgreSQL

Instalação via código-fonte

[#] Definição de variáveis gerais:

# Variável de ambiente para versão completa do Postgres via prompt:


read -p \
'Digite o número de versão completo (X.Y) do PostgreSQL a ser baixado: ' \
PGVERSION

# Definição da variável de ambiente para a versão majoritária:


export PGMAJOR=`echo ${PGVERSION} | cut -f1 -d.`

# Diretório de instalação do PostgreSQL


PGHOME="/usr/local/pgsql/${PGMAJOR}"

# Diretório de binários
PGBIN="${PGHOME}/bin"

# Diretório home do usuário postgres


PGUSERHOME="/var/local/pgsql"

# Diretório de logs
PGLOG="/var/log/pgsql/${PGMAJOR}"

# Diretório de dados do PostgreSQL


PGDATA="${PGUSERHOME}/${PGMAJOR}/data"

# Diretório de logs de transação


PGWAL="${PGUSERHOME}/${PGMAJOR}/wal"

# Diretório de estatísticas temporárias


PG_STAT_TEMP="${PGUSERHOME}/${PGMAJOR}/pg_stat_tmp"

# Diretório de headers C
PGINCLUDEDIR="/usr/local/include/pgsql/${PGMAJOR}"
30 3. Instalação do PostgreSQL

[#] Variáveis de ambiente para o processo de compilação:

# Variável de ambiente do executável Python 3


export PYTHON=`which python3`

# Opções do configure
CONFIGURE_OPTS="
--prefix=${PGHOME} \
--with-python \
--with-libxml \
--with-openssl \
--with-ldap \
--with-uuid=e2fs \
--includedir=${PGINCLUDEDIR}
"

# Protege o processo principal do OOM Killer


CPPFLAGS="-DLINUX_OOM_SCORE_ADJ=0"

# Número de jobs conforme a quantidade de cores da CPU (cores + 1):


NJOBS=`expr \`nproc\` + 1`

# Opções do make
MAKEOPTS="-j${NJOBS}"

# Tipo de hardware
CHOST="x86_64-unknown-linux-gnu"

# Flags de otimização para o make


CFLAGS="-march=native -O2 -pipe"
CXXFLAGS="$CFLAGS"

[#] Criação de um grupo de sistema:

groupadd -r postgres &> /dev/null


31 3. Instalação do PostgreSQL

[#] Criação de usuário de sistema:

useradd \
-c 'PostgreSQL system user' \
-s /bin/bash \
-k /etc/skel \
-d ${PGUSERHOME} \
-g postgres \
-m -r postgres &> /dev/null

[#] Criar o arquivo .pgvars com seu respectivo conteúdo no diretório do usuário home postgres:

cat << EOF > ~postgres/.pgvars


# Environment Variables ======================================================

# PostgreSQL major version


PGMAJOR="${PGMAJOR}"

# Postgre=SQL installation directory


PGHOME="/usr/local/pgsql/\${PGMAJOR}"

# Binary directory
PGBIN="${PGHOME}/bin"

# Library directories
export LD_LIBRARY_PATH="\${PGHOME}/lib:\${LD_LIBRARY_PATH}"

# Manuals directories
export MANPATH="\${PGHOME}/man:\${MANPATH}"

# Path to binaries
export PATH="\${PGBIN}:\${PATH}"

# PostgreSQL data directory


export PGDATA="${PGUSERHOME}/\${PGMAJOR}/data"

# Unset variables
unset PGMAJOR PGHOME PGBIN
EOF
32 3. Instalação do PostgreSQL

[#] Adiciona linha no arquivo de perfil do usuário postgres para ler o arquivo ~/.pgvars e
aplicá-las:

if [ -f ~postgres/.bash_profile ]; then
echo -e "\nsource ~/.pgvars" >> ~postgres/.bash_profile
else
echo -e "\nsource ~/.pgvars" >> ~postgres/.profile
fi

[#] ~postgres/.psqlrc otimizado:

cat << EOF > ~postgres/.psqlrc


\set HISTCONTROL ignoreboth
\set COMP_KEYWORD_CASE upper
\x auto
EOF

[#] Criação de diretórios:

mkdir -pm 0700 ${PGLOG} ${PGDATA} ${PGWAL} ${PG_STAT_TEMP}

[#] Download do código-fonte:

wget -c \
https://ftp.postgresql.org/pub/source/v${PGVERSION}/postgresql-\
${PGVERSION}.tar.bz2 -P /tmp/

[#] Ir para /tmp onde o arquivo foi baixado, descompactá-lo:

cd /tmp/ && tar xf postgresql-${PGVERSION}.tar.bz2

[#] Após a descompactação acessar a pasta do código-fonte:

cd postgresql-${PGVERSION}
33 3. Instalação do PostgreSQL

[#] Processo de configure:

./configure ${CONFIGURE_OPTS}

[#] Compilação (com manuais e contrib):

make world

[#] Instalação:

make install-world

[#] Criação de arquivo de serviço SystemD do PostgreSQL:

cat << EOF > /lib/systemd/system/postgresql-${PGMAJOR}.service


[Unit]
Description=PostgreSQL ${PGMAJOR} database server
After=syslog.target
After=network.target
[Service]
Type=forking
User=postgres
Group=postgres
Environment=PGDATA="${PGDATA}"
Environment=LD_LIBRARY_PATH="${PGHOME}/lib:\${LD_LIBRARY_PATH}"
OOMScoreAdjust=-1000
ExecStart=${PGBIN}/pg_ctl start -D \${PGDATA} -s -w -t 300
ExecStop=${PGBIN}/pg_ctl stop -D \${PGDATA} -s -m fast
ExecReload=${PGBIN}/pg_ctl reload -D \${PGDATA} -s
TimeoutSec=300
[Install]
WantedBy=multi-user.target
EOF

[#] Dar propriedade ao usuário e grupo postgres aos diretórios:

chown -R postgres: /var/{log,local}/pgsql


34 3. Instalação do PostgreSQL

[#] Criação de cluster (instância):

su - postgres -c "\
initdb \
-D ${PGDATA} \
-E utf8 \
-U postgres \
-k \
--locale=pt_BR.utf8 \
--lc-collate=pt_BR.utf8 \
--lc-monetary=pt_BR.utf8 \
--lc-messages=en_US.utf8 \
-T portuguese \
-X ${PGWAL}"

[#] Alterações no postgresql.conf via sed:

# listen_addresses = '*'
sed "s:\(^#listen_addresses.*\):\1\nlisten_addresses = '*':g" \
-i ${PGDATA}/postgresql.conf

# log_destination = 'stderr'
sed "s:\(^#log_destination.*\):\1\nlog_destination = 'stderr':g" \
-i ${PGDATA}/postgresql.conf

# logging_collector = on
sed "s:\(^#logging_collector.*\):\1\nlogging_collector = on:g" \
-i ${PGDATA}/postgresql.conf

# log_filename (nova linha descomentada)


sed "s:\(^#\)\(log_filename.*\):\1\2\n\2:g" \
-i ${PGDATA}/postgresql.conf

# log_directory = '${PGLOG}'
sed "s:\(^#log_directory.*\):\1\nlog_directory = '${PGLOG}':g" \
-i ${PGDATA}/postgresql.conf

# stats_temp_directory = '${PG_STAT_TEMP}'
sed \
"s:\(^#stats_temp_directory.*\):\1\nstats_temp_directory = '${PG_STAT_TEMP}':g" \
-i ${PGDATA}/postgresql.conf
35 3. Instalação do PostgreSQL

[#] Montagem da linha para montagem em memória RAM:

echo -e \
"\ntmpfs ${PG_STAT_TEMP} tmpfs size=32M,uid=postgres,gid=postgres 0 0"\
>> /etc/fstab

[#] Monta tudo definido em /etc/fstab:

mount -a

[#] Habilita e inicializa o serviço do PostgreSQL:

systemctl enable --now postgresql-${PGMAJOR}

Observação

Se a versão do SystemD instalada no servidor for muito antiga, será necessário fazer as
operações de enable e start separadamente.

Limpeza de pacote desnecessários

Terminada a instalação, por questões de boas práticas de segurança, devemos remover os


pacotes utilizados conforme o tipo de distribuição Linux.

• Debian

[#] Desinstalação de pacotes Debian:

apt purge -y ${PKG_RM} ${PKG_DEB}

• Red Hat

[#] Desinstalação de pacotes Red Hat:


36 3. Instalação do PostgreSQL

dnf erase -y ${PKG_RM} ${PKG_RH}


37 3. Instalação do PostgreSQL

SSH sem senha


Para facilitar o acesso ao servidor, é interessante que seja configurado seu acesso SSH sem
senha.
Esse processo consiste em enviar a chave pública do usuário de origem para o servidor de
destino.

[$] Armazenar o endereço na variável de ambiente:

read -p 'Digite o endereço do Servidor PostgreSQL: ' PGSERVER

[$] Caso não exista a chave na máquina local, ela será criada:

if [ ! -e ~/.ssh/id_rsa ]; then
ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa;
fi

[$] Enviar a chave pública do usuário local para o usuário root no servidor de destino:

ssh-copy-id root@${PGSERVER}
38 3. Instalação do PostgreSQL

Revisão
1. P: Quais as vantagens de usar pacotes provenientes do PGDG?
R:
2. P: Quais os nomes dos diretórios de WAL, bancos de dados e tablespaces, dentro do
diretório de dados?
R:
3. P: Quais os nomes dos arquivos de configuração e autenticação?
R:
4. P: É possível ter mais de uma versão majoritária do PostgreSQL na mesma máquina?
R:
5. P: É possível ter mais de uma instância da mesma versão majoritária do PostgreSQL
em uma máquina?
R:
4
Noções básicas
• Conectando à uma base de dados com o psql
• Bases de dados no PostgreSQL
• Tablespaces
• Schemas

39
40 4. Noções básicas

Conectando à uma base de dados com o psql


O psql é o aplicativo cliente padrão do PostgreSQL.
Funciona como um shell interativo, mas também pode executar scripts SQL.
É uma ferramenta muito poderosa, com recurso de auto completar comandos, além de contar
com comandos próprios (meta-comandos) para agilizar o trabalho de um DBA, oferecendo até
mesmo ajuda para comandos SQL.

Parâmetros de conexão do psql

Parâmetro curto Parâmetro longo Descrição


-h --host Hostname ou IP
-p --port Porta
-U --username Usuário
-d --database Base de dados

Seu comportamento de conexão tem a seguinte prioridade:


parâmetros > variáveis de ambiente > nome de usuário de sistema operacional.
Se o parâmetro de banco de dados for omitido, será levado em conta o nome do usuário.

[$] Conectando à base de dados:

psql -d postgres -h localhost -U postgres -p 5432

[$] Sair do psql:

<Ctlr> + D

[$] Conectar com usuário “postgres” à base de dados “postgres”:

psql -U postgres

[>] Criação de um segundo usuário com senha:


41 4. Noções básicas

CREATE USER aluno SUPERUSER PASSWORD '123456';

[>] Sair do psql:

\q

[$] Agora sim podemos nos conectar de qualquer usuário do sistema operacional por meio de
uma conexão TCP:

psql -d postgres -h localhost -U aluno -p 5432

Password for user aluno:


psql (13.2 (Debian 13.2-1.pgdg100+1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

[>] Verificando a versão do Postgres com a função SQL version:

SELECT version();

version
----------------------------------------------------------------------------------------
PostgreSQL 13.2 on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit

[>] Help de todos meta-comandos do psql:

\?

. . .

[>] Listando bases de dados:

\l

List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+------------+------------+-----------------------
42 4. Noções básicas

postgres | postgres | UTF8 | pt_BR.utf8 | pt_BR.utf8 |


template0 | postgres | UTF8 | pt_BR.utf8 | pt_BR.utf8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | pt_BR.utf8 | pt_BR.utf8 | =c/postgres +
| | | | | postgres=CTc/postgres

[>] Criando uma tabela temporária de teste:

CREATE TEMP TABLE tb_teste (


id_ serial primary key,
campo int);

[>] Listando tabelas:

\dt

List of relations
Schema | Name | Type | Owner
-----------+----------+-------+----------
pg_temp_3 | tb_teste | table | postgres

[>] Estrutura de uma tabela:

\d tb_teste

Table "pg_temp_3.tb_teste"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------------------------------------
id_ | integer | | not null | nextval('tb_teste_id__seq'::regclass)
campo | integer | | |
Indexes:
"tb_teste_pkey" PRIMARY KEY, btree (id_)

[>] O psql também dá uma ajuda para comandos SQL:

\h CREATE INDEX

Command: CREATE INDEX


Description: define a new index
Syntax:
CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] name ] ON [ ONLY ] table_name [ USING method ]
( { column_name | ( expression ) } [ COLLATE collation ] [ opclass [ ( opclass_parameter = value [, ... ]
[ INCLUDE ( column_name [, ...] ) ]
[ WITH ( storage_parameter [= value] [, ... ] ) ]
[ TABLESPACE tablespace_name ]
[ WHERE predicate ]
43 4. Noções básicas

URL: https://www.postgresql.org/docs/13/sql-createindex.html
44 4. Noções básicas

[>] Dentro do shell psql se conectar à base template1:

\c template1

[>] Para sair do psql use \q ou <Ctrl> + <D>:

\q

[$] Executando comandos externamente no psql:

psql -d postgres -h localhost -U aluno -p 5432 -Atqc 'SELECT 5 + 2;'

[$] Alternativamente, podemos utilizar string de conexão estilo libpq:

psql 'dbname=postgres host=localhost user=aluno port=5432' -Atqc 'SELECT 5 + 2;'

[$] Quando o nome da base de dados é omitido, o psql utiliza o nome do usuário:

psql -U postgres -Atqc 'SELECT current_database();'

postgres
45 4. Noções básicas

[$] Quando o nome da base de dados e do usuário são omitidos, utiliza-se o nome do usuário
do sistema operacional:

# Verificar o nome da base de dados atual


psql -Atqc 'SELECT current_database();'

postgres

# Verificar o nome do usuário atual


psql -Atqc 'SELECT current_user;'

postgres

[$] psql executando o retorno de uma saída em tela:

echo 'SELECT 9' | psql -Atq

[$] psql executando o retorno de uma saída em tela de um heredoc:

cat << EOF | psql -Atq


SELECT 'foo';
SELECT 'bar';
SELECT 'baz';
EOF

foo
bar
baz

[$] Criando um simples arquivo SQL:

echo "SELECT version();" > /tmp/teste.sql


46 4. Noções básicas

[$] Duas formas de executar um script SQL:

# Sinal de menor
psql -Atq < /tmp/teste.sql

# Parâmetro -f
psql -Atqf /tmp/teste.sql

PostgreSQL 13.2 on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit

Variáveis de ambiente de conexão

As seguintes variáveis de ambiente podem ser usadas para definir valores padrões de conexão
que são utilizadas pelas funções da libpq PQconnectdb, PQsetdbLogin e PQsetdb se não houver
nenhum outro valor especificado pelo código de chamada.
São úteis para evitar embutir informações de conexão de banco de dados em aplicações de
clientes simples.

https://www.postgresql.org/docs/current/static/libpq-envars.htm
47 4. Noções básicas

Bases de dados no PostgreSQL


Bases de dados ou simplesmente bancos de dados é o tipo de objeto contido em uma instância
que permite conexões. Nele, também existem outros tipos de objetos de forma direta, tais
como schemas, languages (linguagens procedurais) ou extensions (extensões).

Bancos de dados padrão e templates

Cada instância (cluster ) do PostgreSQL tem por padrão três bancos de dados:

• postgres: administrativo padrão;

• template1: modelo (template) padrão para criação de outras bases de dados;

• template0: imutável e não conectável. É a base de dados mais pura de uma instância
Postgres.

Templates de bancos de dados

Toda nova base de dados no PostgreSQL é criada a partir de alguma outra.


Essa outra base de dados que é copiada como modelo chamamos de template.
Por padrão, a base de dados usada como template é template1.
A base de dados template0 é o melhor modelo para bases de dados que devam ser puras, ou
seja, que não tenham objeto algum, pois é imutável e sequer permite conexões.
Um banco de dados, quando é tomado como template, tem todos seus objetos internos
copiados para o novo banco de dados que está sendo criado.
Para que uma base de dados possa ser clonada por qualquer usuário com o privilégio CREATEDB,
é preciso que essa base tenha a propriedade istemplate / IS_TEMPLATE como true. Se for false
(padrão), apenas superusuários ou o proprietário da base poderão tomá-la como template.

[>] Via psql, conecte-se ao banco template1:

\c template1

[>] Crie uma tabela de teste dentro de template1:

CREATE TABLE tb_foo();


48 4. Noções básicas

[>] Crie uma nova base:

CREATE DATABASE db_foo;

[>] Conecte à nova base:

\c db_foo

[>] Liste as tabelas:

\dt

List of relations
Schema | Name | Type | Owner
--------+--------+-------+----------
public | tb_foo | table | postgres

Não foi criada explicitamente qualquer tabela na base, mas implicitamente essa nova base foi
criada a partir de template1.

[>] Conectar ao banco template1:

\c template1

[>] Apagar a tabela de teste:

DROP TABLE tb_foo;

[>] Apagar o banco db_foo:

DROP DATABASE db_foo;

[>] Criar novamente a base:


49 4. Noções básicas

CREATE DATABASE db_foo;


50 4. Noções básicas

[>] Conectar à base db_foo:

\c db_foo

[>] Listar suas tabelas:

\dt

Did not find any relations.

Nenhuma tabela listada.

[>] Criação de uma tabela de teste:

CREATE TABLE tb_1();

[>] Listando tabelas na base:

\dt

List of relations
Schema | Name | Type | Owner
--------+------+-------+----------
public | tb_1 | table | postgres

[>] Criar o banco de dados db_bar usando db_foo como template:

CREATE DATABASE db_bar TEMPLATE db_foo;

[>] Conectar à nova base:

\c db_bar
51 4. Noções básicas

[>] Listar tabelas:

\dt

List of relations
Schema | Name | Type | Owner
--------+------+-------+----------
public | tb_1 | table | postgres

Tabela tb_1 foi copiada da base db_foo, que foi usada como template.

[>] Apagando a base postgres:

DROP DATABASE postgres;

[>] Recriando a base de dados postgres a partir de template0:

CREATE DATABASE postgres TEMPLATE template0;

[>] Criação de dois usuários de teste com atributo CREATEDB:

CREATE ROLE user_test1 LOGIN CREATEDB PASSWORD '123456';


CREATE ROLE user_test2 LOGIN CREATEDB PASSWORD '123456';

[>] Conecte à base postgres como user_test1:

\c postgres user_test1

[>] Crie uma base de dados:

CREATE DATABASE db1;

[>] Conecte à base db_one como user_test1:


52 4. Noções básicas

\c db1 user_test1
53 4. Noções básicas

[>] Verifique o proprietário do banco e a propriedade istemplate via consulta em catálogo:

SELECT
u.usename AS proprietario,
d.datistemplate AS base_de_dados_e_template
FROM pg_database AS d
INNER JOIN pg_user AS u
ON (d.datdba = u.usesysid)
WHERE datname = 'db1';

proprietario | base_de_dados_e_template
--------------+--------------------------
user_test1 | f

Resultado f, de false, o que significa que a base não é um template.

[>] Criação de uma tabela dentro da base:

CREATE TABLE tb1();

[>] Criação de uma nova base a partir de db1:

CREATE DATABASE db_one TEMPLATE db1;

[>] Conecte à base db_one como user_test1:

\c db_one user_test1

[>] Verifique as tabelas:

\dt

List of relations
Schema | Name | Type | Owner
--------+------+-------+------------
public | tb1 | table | user_test1

Mesmo a base não tendo a propriedade istemplate, como true, foi possível fazê-la de template
porque a clonagem foi feita por seu proprietário.
54 4. Noções básicas

[>] Conecte à base postgres como user_test2:

\c postgres user_test2

[>] Tentativa de criação de uma nova base a partir de db1:

CREATE DATABASE db_two TEMPLATE db1;

ERROR: permission denied to copy database "db1"

[>] Conecte à base postgres como user_test1:

\c postgres user_test1

[>] Altere a base de dadps db1 para ser template:

ALTER DATABASE db1 IS_TEMPLATE TRUE;

[>] Conecte à base postgres como user_test2:

\c postgres user_test2

[>] Nova tentativa de criação de uma nova base a partir de db1:

CREATE DATABASE db_two TEMPLATE db1;

Dessa vez não houve erro, pois a propriedade istemplate era verdadeira.

[>] Conectar à base postgres como user_test1:


55 4. Noções básicas

\c postgres user_test1
56 4. Noções básicas

[>] Tentativa de apagar a base de dados db1:

DROP DATABASE db1;

ERROR: cannot drop a template database

Um banco de dados modelo não pode ser apagado.

[>] Alterar a propriedade istemplate da base de dados db1 para false:

ALTER DATABASE db1 IS_TEMPLATE FALSE;

[>] Após alterar a propriedade istemplate para true é possível apagar a base de dados:

DROP DATABASE db1;


57 4. Noções básicas

Propriedades de bancos de dados

Ao criarmos ou alterarmos uma base de dados há propriedades que podemos definir.

Sintaxe de CREATE DATABASE:

CREATE DATABASE name


[ [ WITH ] [ OWNER [=] user_name ]
[ TEMPLATE [=] template ]
[ ENCODING [=] encoding ]
[ LOCALE [=] locale ]
[ LC_COLLATE [=] lc_collate ]
[ LC_CTYPE [=] lc_ctype ]
[ TABLESPACE [=] tablespace_name ]
[ ALLOW_CONNECTIONS [=] allowconn ]
[ CONNECTION LIMIT [=] connlimit ]
[ IS_TEMPLATE [=] istemplate ] ]

Nem todas opções estão disponíveis para ALTER DATABASE, tendo apenas dispoíveis:
TABLESPACE, ALLOW_CONNECTIONS, CONNECTION LIMIT e IS_TEMPLATE.

• OWNER
Define um proprietário (user_name) para o banco de dados;

• TEMPLATE
Especifica um banco de dados (template) como template;

• ENCODING
Codificação de caracteres para o novo banco de dados;

• LOCALE
É um atalho para definir LC_COLLATE e LC_CTYPE de uma só vez;

• LC_COLLATE
Ordem de agrupamento de caracteres (collate). Afeta a ordem de classificação em
consultas com ORDER BY, assim como a ordem usada em índices de colunas de texto. O
padrão é pegar o valor do banco de dados template;

• LC_CTYPE
Classificação de caracteres, afeta a categorização de caracteres, sejam eles letras maiús-
culas, letras minúsculas ou dígito;

• TABLESPACE
Tablespace onde o banco de dados estará fisicamente;
58 4. Noções básicas

• ALLOW_CONNECTIONS
Permite ou não conexões ao banco.
É muito útil quando não se quer novas conexões ao banco, mantendo as que já estavam;

• CONNECTION LIMIT
Quantas conexões podem ser feitas à base para usuários que não tenham o atributo
SUPERUSER;

• IS_TEMPLATE
Se seu valor for verdadeiro (true) a base de dados poderá ser clonada como template
por qualquer usuário com o atributo CREATEDB, caso contrário, somente superusuários e
seu dono poderão tomar a base de dados como template.
59 4. Noções básicas

Tablespaces
Tablespace é a localização, no sistema de arquivos, onde objetos do banco de dados são
criados.
Esse recurso permite que administradores de banco de dados criem e/ou alterem objetos em
tablespaces diferentes do padrão.
O tablespace padrão reside no diretório $PGDATA.
Uma vez criado, o tablespace pode ser referido pelo seu nome.
Tablespaces são muito úteis para gerenciamento de armazenamento e performance de discos.
Por exemplo, uma determinada tabela que é muito mais acessada em um sistema do que as
demais talvez possa ser alocada em um tablespace, cujo diretório seja um ponto de montagem
(Unix like) em um outro disco. A ideia é evitar concorrência de I/O, ou seja, é um recurso
extremamente útil na obtenção de maior desempenho.
Dois tablespaces são criados automaticamente quando uma instância é inicializada.
O tablespace pg_global é usado para catálogos de sistema compartilhados, enquanto que o
pg_default é o tablespace padrão das bases de dados template1 e template0 (e, portanto, será
o tablespace padrão para outras bases de dados também, a não ser que seja especificada a
cláusula TABLESPACE em CREATE DATABASE).

Resumidamente, tablespaces têm como propósitos principais:

• Expandir o espaço de armazenamento disponível para o PostgreSQL com mais volumes;


• Fazer perfis de armazenamentos físicos conforme o tipo (SSD, HDD, . . . );
• Agrupar parâmetros de I/O.

[#] Criação de diretório, onde serão criados os tablespaces com o modo de permissão 0700:

mkdir -m 0700 /var/db_storage

[#] Mudar o proprietário para o usuário e grupo postgres:

chown postgres: /var/db_storage

[#] Virar usuário postgres:

su - postgres
60 4. Noções básicas

[$] Criar 3 (três) diretórios de tablespaces:

mkdir /var/db_storage/ts_{alpha,beta,gamma}

[$] Criação dos tablespaces:

cat << EOF | psql


CREATE TABLESPACE ts_alpha LOCATION '/var/db_storage/ts_alpha';
CREATE TABLESPACE ts_beta LOCATION '/var/db_storage/ts_beta';
CREATE TABLESPACE ts_gamma LOCATION '/var/db_storage/ts_gamma';
EOF

[$] Verificando o diretório pg_tblspc, que está dentro do diretório de dados ($PGDATA):

ls -lhd $PGDATA/pg_tblspc/*

lrwxrwxrwx 1 postgres postgres 24 Jan 13 16:12 /var/local/pgsql/13/data/pg_tblspc/16418 -> /var/db_storage/ts_


lrwxrwxrwx 1 postgres postgres 23 Jan 13 16:12 /var/local/pgsql/13/data/pg_tblspc/16419 -> /var/db_storage/ts_
lrwxrwxrwx 1 postgres postgres 24 Jan 13 16:12 /var/local/pgsql/13/data/pg_tblspc/16420 -> /var/db_storage/ts_

[$] Entrar no psql:

psql

[>] Via consulta, exibir os tablespaces criados com seu oid e nome:

SELECT oid, spcname FROM pg_tablespace WHERE spcname ~ '^ts_';

oid | spcname
-------+----------
16418 | ts_alpha
16419 | ts_beta
16420 | ts_gamma

Note que os oid são os nomes dos links.

[>] Criação de banco de dados de teste:


61 4. Noções básicas

CREATE DATABASE db_ts;

[>] Conexão ao banco de dados:

\c db_ts

[>] Criação de tabela:

CREATE TABLE tb_teste (


id serial PRIMARY KEY
USING INDEX TABLESPACE ts_alpha, -- tablespace do índice
campo_2 text,
campo_3 int)
TABLESPACE ts_beta; -- tablespace da tabela

[>] Detalhes da tabela criada:

\d tb_teste

Table "public.tb_teste"
Column | Type | Collation | Nullable | Default
---------+---------+-----------+----------+--------------------------------------
id | integer | | not null | nextval('tb_teste_id_seq'::regclass)
campo_2 | text | | |
campo_3 | integer | | |
Indexes:
"tb_teste_pkey" PRIMARY KEY, btree (id), tablespace "ts_alpha"
Tablespace: "ts_beta"

[>] Populando a tabela:

INSERT INTO tb_teste (campo_2, campo_3)


SELECT
md5(random()::text), -- campo_2
generate_series(1, 5000000); -- campo_3

[>] Via meta-comandos do psql, verificar tamanhos de índices e tabelas:

\dit+
62 4. Noções básicas

List of relations
Schema | Name | Type | Owner | Table | Persistence | Size | Description
--------+---------------+-------+----------+----------+-------------+--------+-------------
public | tb_teste | table | postgres | | permanent | 365 MB |
public | tb_teste_pkey | index | postgres | tb_teste | permanent | 107 MB |

[>] Dentro do psql, com comandos de shell do sistema operacional, verificar os tamanhos dos
diretórios de tablespaces:

\! du -hs /var/db_storage/*

108M /var/db_storage/ts_alpha
366M /var/db_storage/ts_beta
8.0K /var/db_storage/ts_gamma

[>] Mudar a tabela de tablespace:

ALTER TABLE tb_teste SET TABLESPACE ts_gamma;

[>] Novamente, verificar os tamanhos dos diretórios de tablespaces:

\! du -hs /var/db_storage/*

108M /var/db_storage/ts_alpha
12K /var/db_storage/ts_beta
366M /var/db_storage/ts_gamma

Observa-se as mudanças comparando com a checagem anterior.

Aviso

A operação de mover objetos entre tablespaces é demorada, efetue lock nos objetos envolvidos
e gere grande carga de IO.
Recomenda-se que este tipo de operação seja executado em janela de manutenção.
63 4. Noções básicas

Movendo tablespaces

É possível mover tablespaces para outros locais manualmente desde que o PostgreSQL esteja
parado.
Mova o diretório do tablespace para outro local e, então, ajuste o respectivo link simpólico
de pg_tblspc para apontar para o novo local (diretório ou ponto de montagem).
64 4. Noções básicas

Schemas
Também conhecido como namespace, é uma forma lógica de organizar objetos dentro de um
banco de dados, permitindo que um mesmo tipo de objeto seja criado mais de uma vez com
nome igual, mas em schemas diferentes.
Hierarquicamente, é a estrutura logo abaixo de uma base de dados, a qual contém outros tipos
de objetos como tabelas, views, funções e outros.
Quando criamos objetos, os mesmos pertencem ao schema público (public).
Sendo assim, quando fazemos uma consulta, não precisamos especificar o schema, pois está
como padrão no search_path.
Todo schema criado é registrado no catálogo de sistema pg_namespace.

search_path

É um parâmetro cujo contexto (user) permite a qualquer usuário alterá-lo durante a sessão.
Seu objetivo é definir “caminhos padrões” de busca de nomes de schemas.
Alterar o search_path facilita a referência a objetos, dispensando o uso de nomes qualificados
(schmea.objeto). No entanto, apesar de termos esse recurso disponível, é aconselhável não
alterá-lo utilizando nomes qualificados.
Há exceções para isso, quando, por exemplo, em uma determinada aplicação desejamos colocar
seus dados iniciais em um schema diferente.

[>] Verificando o search path:

SHOW search_path;

search_path
-----------------
"$user", public

O retorno do comando mostra duas opções que respectivamente são um schema, com o próprio
nome do usuário seguido por public.

[>] Criação de um banco de dados de teste:

CREATE DATABASE db_schema;


65 4. Noções básicas

[>] Conectar ao novo banco de dados:

\c db_schema

[>] Criação de um schema de teste:

CREATE SCHEMA sc_teste;

[>] Alterar o search_path padrão colocando o schema criado como prioritário:

SET search_path = sc_teste, "$user", public;

[>] Criação de uma tabela:

CREATE TABLE tb_teste();

[>] Listar tabelas:

\dt

List of relations
Schema | Name | Type | Owner
----------+----------+-------+----------
sc_teste | tb_teste | table | postgres

Observa-se que a tabela criada pertence ao schema sc_teste.

[>] Voltando o padrão search_path ao valor padrão:

RESET search_path;

[>] Verificar o search path:


66 4. Noções básicas

SHOW search_path;

search_path
-----------------
"$user", public

[>] Listar tabelas:

\dt

Nada foi listado, pois o search_path foi alterado e agora temos que listar pelo próprio schema.

[>] Listar tabelas:

\dt sc_teste.*

List of relations
Schema | Name | Type | Owner
----------+----------+-------+----------
sc_teste | tb_teste | table | postgres

Ao especificar o schema, é possível listar o que há dentro dele.

[>] Criação de uma tabela:

CREATE TABLE tb_teste();

[>] Listar tabelas via comando SQL:

SELECT schemaname, relname FROM pg_stat_user_tables;

schemaname | relname
------------+----------
public | tb_teste
sc_teste | tb_teste

Nota-se que há duas tabelas de mesmo nome, mas, por estarem em namespaces diferentes,
isso é possível.
67 4. Noções básicas

[>] Criação de uma tabela diretamente em seu namespace:

CREATE TABLE sc_teste.tb_teste2 ();


68 4. Noções básicas

[>] Listar novamente as tabelas via comando SQL:

SELECT schemaname, relname FROM pg_stat_user_tables;

schemaname | relname
------------+-----------
public | tb_teste
sc_teste | tb_teste2
sc_teste | tb_teste

[>] Tentativa de apagar o schema criado:

DROP SCHEMA sc_teste;

ERROR: cannot drop schema sc_teste because other objects depend on it


DETAIL: table sc_teste.tb_teste depends on schema sc_teste
table sc_teste.tb_teste2 depends on schema sc_teste
HINT: Use DROP ... CASCADE to drop the dependent objects too.

A princípio, não foi possível apagar o schema, pois ele não está vazio.

[>] Tentativa de apagar o schema criado:

DROP SCHEMA sc_teste CASCADE;

NOTICE: drop cascades to 2 other objects


DETAIL: drop cascades to table sc_teste.tb_teste
drop cascades to table sc_teste.tb_teste2

Dessa vez, não houve erro. Foi utilizada a cláusula CASCADE, que permitiu que todos objetos
dentro do namespace também fossem apagados.
69 4. Noções básicas

Revisao
1. P: Metacomandos do psql são executados pelo servidor como SQL?
R:
2. P: Quais são as vantagens do uso de tablespaces?
R:
3. P: É possível criar bancos de dados e tablespaces dentro de schemas?
R:
4. P: Como posso mudar a lista de schemas usadas para uma sessão?
R:
5. P: Em qual schema objetos novos são criados?
R:
5
Autenticação e autorização
• Roles: gerenciamento de usuários no Postgres
• Gerenciamento de permissões
• Autenticação
• RLS: Row Level Security - Segurança em nível de linha

70
71 5. Autenticação e autorização

Roles: Gerenciamento de usuários no Postgres


A palavra “papel” é, no sentido de função, qual papel desempenhará no sistema gerenciador
de banco de dados.
O comando CREATE ROLE adiciona um novo papel ao cluster do PostgreSQL.
Um papel, então, é uma entidade que pode possuir objetos de banco de dados e ter privilégios
de banco de dados. Um papel pode ser considerado um usuário, um grupo ou ambos, depen-
dendo de como é usado.
É necessário ter o privilégio CREATEROLE ou ser superusuário para usar esse comando.
Papéis são definidos no nível de cluster e, então, são válidos em todas as bases de dados no
cluster.
Cada objeto criado por um papel faz com que esse objeto seja propriedade dele. Ou seja, esse
papel tem poder total sobre esse objeto.
Usuário é o tipo de role cuja função é operar o sistema e conta com o atributo LOGIN.
Grupo é o tipo de papel cuja função é agrupar outros papéis.

Prompt psql e roles

O prompt do psql muda conforme o tipo de papel: normal ou superusuário.

Para papéis comuns: nome_da_base=>


Para superusuários (atributo SUPERUSER): nome_da_base=#

Permissões de papéis

• SUPERUSER
Contorna quase todas as verificações de autorização (exceto as cobertas por LOGIN e
REPLICATION);

• LOGIN
Pode estabelecer conexões novas com bancos de dados da instância (desde que exista ao
menos um método de autenticação para ele); grupos são papéis comuns sem a permissão
de login;

• REPLICATION
Pode estabelecer conexões que afetam toda a instância, como backups e replicações;

• CREATEROLE
Pode criar outros papéis (usuários e grupos);

• CREATEDB
Pode criar bases de dados;
72 5. Autenticação e autorização

• BYPASSRLS
Pode contornar políticas de RLS (Row Level Security );

• INHERIT
Pode usar os privilégios adquiridos por outros papéis sobre objetos locais sem executar
SET ROLE.

Papéis usuários podem fazer parte de papéis grupos. Dessa forma, as permissões atribuí-
das ao papel grupo estarão disponíveis para todos os membros dele.

Gerenciando papéis e grupos de usuários

[>] Criação de papéis:

-- Papéis grupos
CREATE ROLE dev; -- Grupo de desenvolvedores
CREATE ROLE sysadm; -- Grupo de administradores de sistema
CREATE ROLE sre; -- Grupo de SRE (Site Reliability Engineering)
CREATE ROLE masters; -- Grupo geral masters
CREATE ROLE dba SUPERUSER; --Grupo com atributo SUPERUSER para DBAs

-- Papel usuário
CREATE ROLE beethoven PASSWORD '123456' LOGIN;

-- Criação papéis já pertencendo a um outro papel


CREATE ROLE bach LOGIN PASSWORD '123456' IN ROLE sre;
CREATE ROLE vivaldi LOGIN PASSWORD '123456' IN ROLE dev;

-- Criação de um papel já pertencendo a outros dois pré existentes


CREATE ROLE mozart LOGIN PASSWORD '123456' IN ROLE dev, sysadm;

[>] Dar o privilégio de role masters aos roles:

GRANT masters TO bach, beethoven, mozart, vivaldi;


73 5. Autenticação e autorização

[>] Listar os papéis:

\du

List of roles
Role name | Attributes | Member of
-----------+------------------------------------------------------------+----------------------
dev | Cannot login | {}
bach | | {sre,masters}
beethoven | | {masters}
sysadm | Cannot login | {}
dba | Superuser, Cannot login | {}
sre | Cannot login | {}
masters | Cannot login | {}
mozart | | {dev,sysadm,masters}
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
vivaldi | | {dev,masters}

Na segunda coluna, referente a atributos, nota-se que alguns papéis constam como “Cannot
login”, ou seja, não pode logar. Portanto, esses papéis são grupos.
Na terceira coluna, é exibido a que outro papel pertence.

[>] Criação de uma base de dados para nossos testes:

CREATE DATABASE db_zero;

[>] Conectar-se a esse novo banco de dados:

\c db_zero

You are now connected to database "db_zero" as user "postgres".

Como diz a mensagem, conexão ao banco de dados db_zero como usuário postgres.

[>] Criação de tabelas de testes:

CREATE TABLE tb_dev(campo1 int);


CREATE TABLE tb_sysadm(campo1 int);
CREATE TABLE tb_sre(campo1 int);
74 5. Autenticação e autorização

[>] Listar as tabelas:

\dt

List of relations
Schema | Name | Type | Owner
--------+-----------+-------+----------
public | tb_dev | table | postgres
public | tb_sysadm | table | postgres
public | tb_sre | table | postgres

Quando um usuário cria um objeto, esse pertence a ele inicialmente.


Nota-se, na quarta coluna - Owner (proprietário) -, que todas tabelas pertencem ao usuário
postgres.

[>] Mudar os proprietários das tabelas:

ALTER TABLE tb_dev OWNER TO dev;


ALTER TABLE tb_sysadm OWNER TO sysadm;
ALTER TABLE tb_sre OWNER TO sre;

[>] Listar as tabelas:

\dt

List of relations
Schema | Name | Type | Owner
--------+-----------+-------+-------
public | tb_dev | table | dev
public | tb_sysadm | table | sysadm
public | tb_sre | table | sre

[>] Conceder o privilégio de beethoven para participar do grupo dba:

GRANT dba TO beethoven;


75 5. Autenticação e autorização

[>] Conectar-se ao banco de dados db_zero como usuário mozart:

\c db_zero mozart

You are now connected to database "db_zero" as user "mozart".

Agora a mensagem informa que a conexão foi feita como usuário mozart.

[>] Verificar role de sessão e role atual:

SELECT session_user, current_user;

session_user | current_user
--------------+--------------
mozart | mozart

[>] Informações do role mozart:

\du mozart

List of roles
Role name | Attributes | Member of
-----------+------------+----------------------
mozart | | {dev,sysadm,masters}

[>] Pode-se também verificar informações de filiação de um role via consulta em catálogo:

SELECT
pg_get_userbyid(roleid) AS membro_de
FROM pg_auth_members
WHERE pg_get_userbyid(member) = 'mozart'
ORDER BY membro_de;

membro_de
-----------
dev
sysadm
masters
76 5. Autenticação e autorização

[>] Para facilitar ainda mais, é possível criar uma função PL/pgSQL:

CREATE OR REPLACE function fc_member_of(role_ text)


RETURNS TABLE (member_of text)
AS $identificador$
BEGIN
RETURN QUERY
SELECT
pg_get_userbyid(roleid)::text
FROM pg_auth_members
WHERE pg_get_userbyid(member) = role_;
RETURN;
END;
$identificador$ LANGUAGE PLPGSQL;

[>] Execução da função para verificar a filiação do papel mozart:

SELECT fc_member_of('mozart');

fc_member_of
--------------
dev
sysadm
masters

[>] INSERTs em tabelas:

INSERT INTO tb_dev (campo1) VALUES (1);


INSERT INTO tb_sysadm (campo1) VALUES (517);

[>] Tentativa de INSERT:

INSERT INTO tb_sre (campo1) VALUES (5448);

ERROR: permission denied for table tb_sre

A tabela pertence ao papel grupo sre e mozart não pertence a esse grupo, então ele não tem
permissão alguma nela.
77 5. Autenticação e autorização

[>] Conectar-se ao banco de dados db_zero como usuário bach:

\c db_zero bach

[>] Informações do role bach:

SELECT fc_member_of('bach');

fc_member_of
--------------
sre
masters

[>] Verifica role de sessão e role atual:

SELECT session_user, current_user;

session_user | current_user
--------------+--------------
bach | bach

[>] Fazer o INSERT que não foi possível com o usuário mozart:

INSERT INTO tb_sre (campo1) VALUES (5448);

Como bach pertence ao grupo sre, não houve problema.

[>] Conectar-se ao banco de dados db_zero como usuário beethoven:

\c db_zero beethoven

[>] Informações do role beethoven:

SELECT fc_member_of('beethoven');
78 5. Autenticação e autorização

fc_member_of
--------------
masters
dba

[>] Verificar role de sessão e role atual:

SELECT session_user, current_user;

session_user | current_user
--------------+--------------
beethoven | beethoven

[>] Tentativa de SELECT nas tabelas:

SELECT * FROM tb_dev;


SELECT * FROM tb_sysadm;
SELECT * FROM tb_sre;

ERROR: permission denied for table . . .

O usuário não foi bem sucedido em nenhuma tabela, pois não pertence ao grupo de nenhuma
delas e nem tem qualquer permissão.

O comando SET ROLE

Redefine o identificador do usuário atual da sessão para um outro papel.


Esse outro papel deve ser que o papel de sessão seja membro. Se ele for um super usuário,
qualquer papel pode ser selecionado.

SET [ SESSION | LOCAL ] ROLE role_name


SET [ SESSION | LOCAL ] ROLE NONE
RESET ROLE

Os modificadores SESSION (padrão) e LOCAL são os mesmos utilizados pelo comando SET.
LOCAL tem seu efeito apenas dentro de uma transação, enquanto SESSION para toda sessão.

[>] Temporariamente como role dba:

SET ROLE dba;


79 5. Autenticação e autorização

O prompt mudou, terminando com um sustenido (#), indicando o privilégio SUPERUSER.


80 5. Autenticação e autorização

[>] Informações de atributos do role dba:

\du dba

List of roles
Role name | Attributes | Member of
-----------+-------------------------+-----------
dba | Superuser, Cannot login | {}

[>] Verificar role de sessão e role atual:

SELECT session_user, current_user;

session_user | current_user
--------------+--------------
beethoven | dba

Agora, o papel de sessão está diferente do papel atual.

[>] Tentativa de SELECT nas tabelas:

SELECT * FROM tb_dev;


SELECT * FROM tb_sysadm;
SELECT * FROM tb_sre;

. . .

Dessa vez, não houve erro, pois o papel atual tem o privilégio SUPERUSER, o que lhe dá poderes
absolutos.

[>] Voltar a ser o role original:

RESET role;

[>] Verificar role de sessão e role atual:

SELECT session_user, current_user;


81 5. Autenticação e autorização

session_user | current_user
--------------+--------------
beethoven | beethoven

[>] Testar dentro de uma transação, executar um comando por vez e observar o resultado:

BEGIN;

-- Mudar role localmente para masters


SET LOCAL role masters;

-- Verificar role de sessão e role atual


SELECT session_user, current_user;

session_user | current_user
--------------+--------------
beethoven | masters

-- Mudar role localmente para dba e observe a mudança no prompt


SET LOCAL role dba;

-- Verificar role de sessão e role atual


SELECT session_user, current_user;

session_user | current_user
--------------+--------------
beethoven | dba

-- Fechar a transação
COMMIT;

[>] Verifica role de sessão e role atual:

SELECT session_user, current_user;

session_user | current_user
--------------+--------------
beethoven | beethoven

A partir do momento em que a transação é fechada, volta ao papel atual que estava antes
dela.
82 5. Autenticação e autorização

[>] Conectar-se como usuário bach:

\c db_zero bach

[>] Verificar role de sessão e role atual:

SELECT session_user, current_user;

session_user | current_user
--------------+--------------
bach | bach

[>] Informações do role bach:

SELECT fc_member_of('bach');

fc_member_of
--------------
sre
masters

[>] Tentativa de bach se tornar o role dba:

SET ROLE dba;

ERROR: permission denied to set role "dba"

Isso aconteceu porque bach não pertence a dba.

[>] Conectar-se ao banco de dados db_zero como usuário beethoven:

\c db_zero beethoven

[>] Temporariamente como role masters:

SET ROLE masters;


83 5. Autenticação e autorização

[>] Verificar role de sessão e role atual:

SELECT session_user, current_user;

session_user | current_user
--------------+--------------
beethoven | masters

[>] Temporariamente como role dba:

SET ROLE dba;

[>] Verificar role de sessão e role atual:

SELECT session_user, current_user;

session_user | current_user
--------------+--------------
beethoven | dba

[>] Tentativa de virar o papel dev:

SET ROLE dev;

ERROR: permission denied to set role "dev"

Transferindo propriedades: REASSIGN OWNED

Através do comando REASSIGN OWNED, podemos transferir a propriedade de todos objetos da base
de dados atual de um usuário para outro.

[>] Temporariamente como role dba:

SET ROLE dba;


84 5. Autenticação e autorização

[>] Tudo o que pertence a dev passará a pertencer a beethoven:

REASSIGN OWNED BY dev TO beethoven;

[>] Listar as tabelas:

\dt

List of relations
Schema | Name | Type | Owner
--------+-----------+-------+-----------
public | tb_dev | table | beethoven
public | tb_sysadm | table | sysadm
public | tb_sre | table | sre

[>] Tudo o que pertence a sysadm e sre passará a pertencer a beethoven:

REASSIGN OWNED BY sysadm, sre TO beethoven;

[>] Listar as tabelas:

\dt

List of relations
Schema | Name | Type | Owner
--------+-----------+-------+-----------
public | tb_dev | table | beethoven
public | tb_sysadm | table | beethoven
public | tb_sre | table | beethoven

[>] Voltar os proprietários originais:

ALTER TABLE tb_dev OWNER TO dev;


ALTER TABLE tb_sysadm OWNER TO sysadm;
ALTER TABLE tb_sre OWNER TO sre;
85 5. Autenticação e autorização

Gerenciamento de permissões
Privilégios

Quando um objeto é criado, é assimilado um dono a ele: normalmente o papel que executou
o comando de criação.
Para a maioria dos tipos de objetos, o estado inicial é que apenas o dono (ou um superusuário)
possa fazer qualquer coisa com o objeto.
Para permitir que outros papéis possam utilizar esse objeto, privilégios têm que ser concedidos.
Os privilégios que são aplicáveis a um objeto em particular variam dependendo do tipo de objeto
(tabela, função, etc.).
Somente o dono do objeto pode modificá-lo ou destruí-lo (DDL).
Um objeto pode ser assimilado a um novo proprietário com o comando ALTER do tipo apropriado
de objeto, e. g. ALTER TABLE.
Superusuários podem sempre fazer isso. No entanto, um papel comum só poderá fazer se for
o atual proprietário do objeto (ou membro de um grupo proprietário) e um membro do novo
papel proprietário.

Tipos de privilégios

SELECT (r)

Permite SELECT em qualquer coluna, ou colunas especificadas por lista, de uma tabela, view
ou sequência.
Também permite o uso de COPY TO.
Esse privilégio é também necessário para referenciar valores existentes de colunas em UPDATE
ou DELETE.
Para sequências, esse privilégio também permite o uso da função currval.
Para grandes objetos (large objects), permite que seja lido.

INSERT (a)

Permite INSERT de um novo registro em uma tabela.


Se colunas específicas forem listadas, apenas nessas será assimilado o privilégio, enquanto as
outras colunas receberão valores padrões.

Também permite COPY FROM.

UPDATE (w)

Permite UPDATE de qualquer coluna, ou em colunas especificadas por lista, de uma tabela
especifica.
Na prática, qualquer UPDATE fora do comum vai requerer também o privilégio SELECT, uma vez
86 5. Autenticação e autorização

que as colunas de uma tabela devem ser referenciadas para determinar quais linhas devem ser
atualizadas, e / ou para calcular novos valores para as colunas.
SELECT ... FOR UPDATE e SELECT ... FOR SHARE também requerem esse privilégio em, pelo menos,
uma coluna, em adição ao privilégio SELECT.
Para sequências, este privilégio permite o uso das funções nextval e setval.
Para grandes objetos (BLOBs), este privilégio permite gravar ou truncar o objeto.

DELETE (d)

Permite DELETE de registros em uma tabela especificada.


Na prática, qualquer DELETE fora do comum vai requerer também o privilégio SELECT, uma
vez que as colunas de uma tabela devem ser referenciadas para determinar que linhas serão
apagadas.

TRUNCATE (D)

Permite o comando TRUNCATE em uma tabela.

REFERENCES (x)

Para criar uma restrição (constraint) chave estrangeira (foreign key ), é necessário ter este
privilégio em ambas as colunas referenciadas.
O privilégio deve ser concedido para todas as colunas de uma tabela ou apenas em específicas.

TRIGGER (t)

Permite a criação de um gatilho (trigger ) em uma tabela especificada.

CREATE (C) Para bases de dados, permite novos esquemas serem criados internamente.

Para esquemas, permite que novos objetos sejam criados dentro dele.
Para renomear um objeto que já existe, deve ser o proprietário do objeto e ter este privilégio
no esquema que o contém.
Para tablespaces, permite que tabelas, índices e arquivos temporários sejam criados dentro
do tablespace. Além disso, permite que bases de dados seja criadas com o tablespace como
padrão.
Se revogado este privilégio, não altera a localização de objetos existentes.

CONNECT (c)

Permite ao usuário se conectar à base especificada.


Este privilégio é checado ao iniciar a conexão em adição à checagem de quaisquer restrições
impostas pelo pg_hba.conf.
87 5. Autenticação e autorização

TEMPORARY/TEMP (T)

Permite a criação de objetos temporários (tabelas, views ou sequências) na base de dados


especificada.

EXECUTE (X)

Permite o uso de uma função especificada e o uso de quaisquer operadores que são implemen-
tados no topo da função.
É o único tipo de privilégio aplicável a funções.

USAGE (U)

Para linguagens procedurais, permite o uso de uma linguagem especificada para criação de
funções.
Para esquemas, permite acesso a objetos (levar em conta também os privilégios dos próprios
objetos).
Essencialmente, isso permite que sejam feitas buscas em objetos dentro do esquema.
Sem essa permissão, ainda é possível ver os nomes de objetos, e. g. consultando tabelas de
sistema.
Também, após revogar essa permissão, backends existentes podem ter statements que tiveram
feito previamente essa busca, então isso não é completamente seguro para prevenir acessos a
objetos.
Para sequências, este privilégio permite o uso das funções currval e nextval.
Para tipos e domínios, este privilégio permite o uso do tipo ou domínio na criação de tabelas,
funções e outros objetos de esquema.
Observe que ele não controla o uso geral de tipo, tal como valores de tipo aparecendo em
consultas, apenas previne que objetos criados dependam desse tipo.
O principal propósito do privilégio é controlar que usuários criem dependências em um tipo, o
que poderia impedir o proprietário de alterar o tipo mais tarde.
Para foreign-data wrappers, este privilégio os habilita a criar novos servidores que utilizem
dados externos encapsulados (foreign-data wrapper ).
Para servidores, este privilégio permite criar, alterar e descartar usuário de seu próprio mapea-
mento de usuários associados com o servidor.
Também habilita a consultar as opções do servidor e mapeamentos de usuários associados.

ALL PRIVILEGES (arwdDxt)

Concede todos os privilégios disponíveis imediatamente.


A palavra-chave PRIVILEGES é opcional no PostgreSQL, mas é requerida pelo padrão SQL.
Os privilégios requeridos por outros comandos são listados na página de referência do respectivo
comando.
88 5. Autenticação e autorização

Gerenciando privilégios

Para realizar o gerenciamento dos privilégios, utilizamos os comandos GRANT e REVOKE:

GRANT
Concede privilégios de acesso a objetos para papéis.

GRANT <privilegio> ON <objeto> TO <role>

REVOKE
Revoga (tira) privilégios de acesso a objetos de papéis.

REVOKE <privilegio> ON <objeto> FROM <role>

Permissões em tabelas

[>] Conectar ao banco de dados db_zero como beethoven:

\c db_zero beethoven

[>] Exibindo informações de privilégios da tabela tb_dev:

\z tb_dev

ou

\dp tb_dev

Access privileges
Schema | Name | Type | Access privileges | Column privileges | Policies
--------+----------+-------+-------------------+-------------------+----------
public | tb_dev | table | | |

[>] Temporariamente como role dba:

SET ROLE dba;


89 5. Autenticação e autorização
90 5. Autenticação e autorização

[>] Como superusuário, dar permissão de SELECT na tabela tb_dev para o role masters:

GRANT SELECT ON TABLE tb_dev TO masters;

Lembrando que beethoven pertence a masters. . .

[>] Reset de role:

RESET role;

[>] Função has_table_privilege informa se um role tem privilégios em uma tabela:

-- O usuário atual, na tabela tb_dev, tem o privilégio de SELECT?


SELECT has_table_privilege(current_user, 'tb_dev', 'SELECT');

has_table_privilege
---------------------
t

-- O usuário atual, na tabela tb_dev, tem o privilégio de INSERT?


SELECT has_table_privilege(current_user, 'tb_dev', 'INSERT');

has_table_privilege
---------------------
f

[>] Exibindo informações de privilégios da tabela tb_dev:

\z tb_dev

Access privileges
Schema | Name | Type | Access privileges | Column privileges | Policies
--------+----------+-------+---------------------+-------------------+----------
public | tb_dev | table | dev=arwdDxt/dev+ | |
| | | masters=r/dev | |
91 5. Autenticação e autorização

[>] Exibindo informações de privilégios da tabela tb_dev via consulta em catálogo:

SELECT
unnest(relacl) AS privilegios
FROM pg_class
WHERE relname = 'tb_dev';

privilegios
---------------------
dev=arwdDxt/dev
masters=r/dev

Primeira linha: role dev tem todos os privilégios que foram concedidos para ele por ser dono
do objeto.
Segunda linha: role master tem o privilégio de SELECT (r) que foi concedido por dev (ou um
superusuário).

Legendas para abreviações de privilégios

Quando consultamos privilégios de um objeto, eles vêm com uma notação própria, conforme
segue abaixo:

• rolename=xxxx: privilégios concedidos a um papel;


• =xxxx: privilégios concedidos a PUBLIC;
• r: SELECT (“read”);
• w: UPDATE (“write”);
• a: INSERT (“append”);
• d: DELETE;
• D: TRUNCATE;
• x: REFERENCES;
• t: TRIGGER;
• X: EXECUTE;
• U: USAGE;
• C: CREATE;
• c: CONNECT;
• T: TEMPORARY;
• arwdDxt: ALL PRIVILEGES (para tabelas, varia para outros objetos);
• *: grant option para o privilégio precedido;
• /yyyy: papel que concedeu esse privilégio.
92 5. Autenticação e autorização

[>] Agora, o role beethoven pode fazer SELECT na tabela tb_dev:

SELECT * FROM tb_dev;

campo1
--------
1

[>] Temporariamente como role dba:

SET ROLE dba;

[>] Dar privilégios nas tabelas tb_sysadm e tb_sre para masters e, por fim, voltar a ser o
role beethoven:

-- SELECT, DELETE e INSERT na tabela tb_sysadm


GRANT SELECT, DELETE, INSERT ON TABLE tb_sysadm TO masters;

-- UPDATE na tabela tb_sre


GRANT UPDATE ON TABLE tb_sre TO masters;

-- Voltar a ser o role beethoven


RESET role;

[>] Verificar os privilégios de cada tabela:

SELECT
relname,
relacl AS privilegios
FROM pg_class
WHERE relname IN ('tb_dev', 'tb_sysadm', 'tb_sre')
ORDER BY relname;

relname | privilegios
-----------+---------------------------------------
tb_dev | {dev=arwdDxt/dev,masters=r/dev}
tb_sysadm | {sysadm=arwdDxt/sysadm,masters=ard/sysadm}
tb_sre | {sre=arwdDxt/sre,masters=w/sre}
93 5. Autenticação e autorização

[>] Testando privilégios:

-- INSERT em tb_sysadm
INSERT INTO tb_sysadm (campo1) VALUES (9885);

-- UPDATE em tb_sysadm
UPDATE tb_sysadm SET campo1 = 0 WHERE campo1 = 9885;

ERROR: permission denied for table tb_sysadm

Erro provocado por não ter a permissão de UPDATE na tabela.

-- UPDATE em tb_sre
UPDATE tb_sre SET campo1 = 0 WHERE campo1 > 100;

ERROR: permission denied for table tb_sre

Dessa vez, apesar de ter a permissão de UPDATE, não tem a de SELECT.

[>] Temporariamente como role dba:

SET ROLE dba;

[>] Dar O privilégio de SELECT na tabelas tb_sre e, por fim, voltar a ser o role beethoven:

-- SELECT na tabela tb_sre


GRANT SELECT ON TABLE tb_sre TO masters;

-- Voltar a ser o role beethoven


RESET role;
94 5. Autenticação e autorização

[>] Verificar os privilégios da tabela tb_sre:

SELECT relacl AS privilegios


FROM pg_class WHERE relname = 'tb_sre';

privilegios
----------------------------------------
{sre=arwdDxt/sre,masters=rw/sre}

Nota-se, agora, “rw” em vez de somente “r” para masters.

[>] Testando o privilégio de UPDATE:

UPDATE tb_sre SET campo1 = 0 WHERE campo1 > 100;

[>] Temporariamente como role dba para fins administrativos:

SET ROLE dba;

[>] Revogar privilégios:

-- Revogar SELECT e UPDATE na tabela tb_sre


REVOKE SELECT, UPDATE ON tb_sre FROM masters;

-- Revogar todos privilégios nas tabelas tb_dev e tb_sysadm


REVOKE ALL ON tb_dev, tb_sysadm FROM masters;

[>] Verificar os privilégios de cada tabela:

SELECT
relname,
relacl AS privilegios
FROM pg_class
WHERE relname IN ('tb_dev', 'tb_sysadm', 'tb_sre')
ORDER BY relname;

relname | privilegios
-----------+-----------------------
tb_dev | {dev=arwdDxt/dev}
tb_sysadm | {sysadm=arwdDxt/sysadm}
95 5. Autenticação e autorização

tb_sre | {sre=arwdDxt/sre}

Pronto, não há mais qualquer menção ao papel master.


96 5. Autenticação e autorização

Permissões em colunas

[>] Adicionar mais uma coluna na tabela tb_dev:

ALTER TABLE tb_dev ADD COLUMN campo2 int;

[>] Permissão total na coluna campo2 para masters:

GRANT ALL (campo2) ON tb_dev TO masters;

[>] Verificar os privilégios da tabela tb_dev:

\z tb_dev

Access privileges
Schema | Name | Type | Access privileges | Column privileges | Policies
--------+----------+-------+---------------------+----------------------+----------
public | tb_dev | table | dev=arwdDxt/dev | campo2: +|
| | | | masters=arwx/dev |

Nota-se, em “Column privileges”, que campo2 tem todos privilégios (de uma coluna) para
masters.

[>] Voltar a ser o role original:

RESET role;

[>] Testes de INSERT e SELECT:

-- Inserir uma linha apenas na coluna campo2


INSERT INTO tb_dev (campo2) VALUES (1);

-- Consulta apenas na coluna campo2


SELECT campo2 FROM tb_dev;

campo2
--------
1
97 5. Autenticação e autorização

[>] E se a consulta fosse na outra coluna?

SELECT campo1 FROM tb_dev;

ERROR: permission denied for table tb_dev

Em campo1, não há qualquer permissão para beethoven.

[>] Função has_column_privilege para verificar o privilégio SELECT nas colunas campo1 e
campo2:

-- campo1
SELECT has_column_privilege(current_user, 'tb_dev', 'campo1', 'SELECT');

has_column_privilege
----------------------
f

-- campo2
SELECT has_column_privilege(current_user, 'tb_dev', 'campo2', 'SELECT');

has_column_privilege
----------------------
t

Permissões em bancos de dados

[>] Temporariamente como role dba para fins administrativos:

SET ROLE dba;


98 5. Autenticação e autorização

[>] Revogar conexão ao banco de dados db_zero para todos:

REVOKE CONNECT ON DATABASE db_zero FROM PUBLIC;

Após a execução do comando, nenhum usuário (exceto super usuários) poderá se conectar a
este banco.
No entanto, as conexões existentes são mantidas. Assim, caso queira “expulsar” um usuário
forçadamente, existe a função de sistema pg_terminate_backend, que tem como parâmetro o pid
do processo da conexão que deseja terminar.

[>] Conceder os privilégios de conectar e criar ao role dev:

GRANT CONNECT, CREATE ON DATABASE db_zero TO dev;

[>] Exibir informações da base de dados db_zero:

\l db_zero

List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
---------+----------+----------+-------------+-------------+-----------------------
db_zero | postgres | UTF8 | pt_BR.UTF-8 | pt_BR.UTF-8 | =T/postgres +
| | | | | postgres=CTc/postgres+
| | | | | dev=Cc/postgres

Em “Access privileges”, podemos verificar as permissões a essa base de dados:

• =T/postgres: a PUBLIC somente privilégio para objetos temporários (T);


• postgres=CTc/postgres: ao role postgres criar (C), objetos temporários (T) e conectar (c);
• dev=Cc/postgres : a role dev criar (C) e conectar (c).
99 5. Autenticação e autorização

[>] Função de sistema has_database_privilege. O usuário beethoven pode se conectar à base


db_zero?

SELECT has_database_privilege('beethoven', 'db_zero', 'CONNECT');

has_database_privilege
------------------------
f

[>] Voltar a ser o role original:

RESET role;

[>] Conectar à base db_zero como vivaldi:

\c db_zero vivaldi

[>] Exibir os roles em que o usuário atual pertence:

\du vivaldi

List of roles
Role name | Attributes | Member of
-----------+------------+-----------------
vivaldi | | {dev,masters}

Por vivaldi ser membro do grupo dev, sua conexão foi bem sucedida.

[>] Tentativa de conexão com o usuário beethoven:

\c db_zero beethoven

FATAL: permission denied for database "db_zero"


DETAIL: User does not have CONNECT privilege.
Previous connection kept

beethoven não tem o privilégio de conectar-se à base.


100 5. Autenticação e autorização

[>] Conectar à base como superusuario:

\c db_zero aluno

[>] Alterar privilégios na base db_zero:

-- Revogar conexão ao banco db_zero para dev


REVOKE CONNECT ON DATABASE db_zero FROM dev;

-- Permitir conexão a PUBLIC (todos)


GRANT CONNECT ON DATABASE db_zero TO PUBLIC;

[>] Privilégios de acesso ao banco db_zero:

SELECT
unnest(datacl) AS permissoes
FROM pg_database
WHERE datname = 'db_zero';

permissoes
-----------------------
=Tc/postgres
postgres=CTc/postgres
dev=C/postgres

[>] Testes de conexão:

\c db_zero beethoven
\c db_zero vivaldi
101 5. Autenticação e autorização

Permissões em schemas

[>] Criação de schema:

CREATE SCHEMA sc_dev AUTHORIZATION dev;

[>] Exibindo o proprietário do schema:

SELECT
r.rolname role_proprietario
FROM pg_namespace AS n
INNER JOIN pg_roles AS r
ON (r.oid = n.nspowner)
WHERE n.nspname = 'sc_dev';

role_proprietario
-------------------
dev

ou via meta-comando psql:

\dn sc_dev

List of schemas
Name | Owner
----------+-------
sc_dev | dev

[>] O role atual é membro de quais outros roles?:

\du vivaldi

List of roles
Role name | Attributes | Member of
-----------+------------+-----------------
vivaldi | | {dev,masters}

Pertence a dev, mesmo dono do schema criado.


102 5. Autenticação e autorização

[>] Criação de tabela de teste dentro do schema criado:

CREATE TABLE sc_dev.tb_teste (campo1 int);

[>] Exibir todas tabelas dentro do schema:

\dt sc_dev.*

List of relations
Schema | Name | Type | Owner
----------+----------+-------+---------
sc_dev | tb_teste | table | vivaldi

[>] Alterar a propriedade da tabela criada para o proprietário dev:

ALTER TABLE sc_dev.tb_teste OWNER TO dev;

[>] Dar todos privilégios na tabela para masters:

GRANT ALL ON TABLE sc_dev.tb_teste TO masters;

[>] Conectar à base db_zero como beethoven:

\c db_zero beethoven

[>] Tentativa de INSERT:

INSERT INTO sc_dev.tb_teste (campo1) VALUES (1);

ERROR: permission denied for schema sc_dev


LINE 1: INSERT INTO sc_dev.tb_teste (campo1) VALUES (1);

INSERT mal sucedido porque, apesar de ter todos privilégios na tabela, não tinha no schema
ao qual pertence.
103 5. Autenticação e autorização

[>] Conectar à base db_zero como vivaldi:

\c db_zero vivaldi

[>] Conceder o privilégio de acessar o schema para masters:

GRANT USAGE ON SCHEMA sc_dev TO masters;

[>] Informações do schema sc_dev:

\dn+ sc_dev

List of schemas
Name | Owner | Access privileges | Description
----------+-------+-------------------+-------------
sc_dev | dev | dev=UC/dev + |
| | masters=U/dev |

[>] Conectar à base db_zero como beethoven:

\c db_zero beethoven

[>] Agora, com as devidas permissões, fazer o INSERT na tabela tb_teste dentro do schema
sc_dev:

INSERT INTO sc_dev.tb_teste (campo1) VALUES (1);

[>] Selecionar todos os dados da tabela:

SELECT * FROM sc_dev.tb_teste;

campo1
--------
1
104 5. Autenticação e autorização

——————– EXTRA ——————–

Privilégios padrão

A forma padrão de privilégios a objetos. Ou seja, mesmo para objetos criados posteriormente,
os privilégios serão mantidos.

[>] Conectar ao banco de dados db_zero como usuário postgres:

\c db_zero postgres

[>] Alterar os privilégios padrão no schema sc_dev para conceder SELECT nas tabelas para
o role master:

ALTER DEFAULT PRIVILEGES IN SCHEMA sc_dev GRANT SELECT ON TABLES TO masters;

[>] Criar tabelas e redefinir proprietário:

-- Criação de tabelas de teste


CREATE TABLE sc_dev.tb_teste2 (campo1 int);
CREATE TABLE sc_dev.tb_teste3 (campo1 int);

-- Deixar as tabelas pertencendo ao role dev


ALTER TABLE sc_dev.tb_teste2 OWNER TO dev;
ALTER TABLE sc_dev.tb_teste3 OWNER TO dev;
105 5. Autenticação e autorização

[>] Verificar privilégios:

SELECT
n.nspname||'.'||c.relname AS tabela,
relacl AS privilegios
FROM pg_class AS c
INNER JOIN pg_namespace AS n
ON (n.oid = c.relnamespace)
WHERE n.nspname = 'sc_dev'
ORDER BY relname;

tabela | privilegios
--------------------+---------------------------------------------
sc_dev.tb_teste | {dev=arwdDxt/dev,masters=arwdDxt/dev}
sc_dev.tb_teste2 | {dev=arwdDxt/dev,masters=r/dev}
sc_dev.tb_teste3 | {dev=arwdDxt/dev,masters=r/dev}

[>] Permissões para objetos posteriores criados pelo role dev para o role master:

-- SELECT para tabelas


ALTER DEFAULT PRIVILEGES FOR ROLE dev GRANT SELECT ON TABLES TO masters;

-- USAGE (acesso) a schemas


ALTER DEFAULT PRIVILEGES FOR ROLE dev GRANT USAGE ON SCHEMAS TO masters;

[>] Criação de um novo schema:

CREATE SCHEMA sc_dev2 AUTHORIZATION dev;

[>] Listar schemas criados:

\dn sc_dev*

List of schemas
Name | Owner
-----------+-------
sc_dev | dev
sc_dev2 | dev

[>] Virar o role dev:

SET role dev;


106 5. Autenticação e autorização

[>] Verificar role de sessão e role atual:

SELECT session_user, current_user;

session_user | current_user
--------------+--------------
postgres | dev

[>] Criação de tabelas:

CREATE TABLE sc_dev2.tb_dev2(campo int);


CREATE TABLE tb_dev2(campo int);

[>] Listando tabelas dentro do schema sc_dev2:

\dt sc_dev2.*

List of relations
Schema | Name | Type | Owner
-----------+-----------+-------+-------
sc_dev2 | tb_dev2 | table | dev

[>] Listando tabelas do schema public:

\dt

List of relations
Schema | Name | Type | Owner
--------+-----------+-------+-------
public | tb_dev | table | dev
public | tb_dev2 | table | dev
public | tb_sysadm | table | sysadm
public | tb_sre | table | sre

[>] Conectar ao banco de dados db_zero como usuário beethoven:

\c db_zero beethoven
107 5. Autenticação e autorização

[>] Verificar role de sessão e role atual:

SELECT session_user, current_user;

session_user | current_user
--------------+--------------
beethoven | beethoven

[>] Como beethoven pertence a masters, as consultas serão bem sucedidas:

SELECT * FROM sc_dev2.tb_dev2;


SELECT * FROM tb_dev2;
108 5. Autenticação e autorização

Autenticação
pg_hba.conf - host-based authentication (autenticação baseada em máquina)

No PostgreSQL, cada nova conexão recebida precisa passar por uma etapa de autenticação
antes de ser permitido o acesso aos dados do banco de dados de destino.
Essa autenticação é baseada em diversas configurações, permissões de usuário e em uma
tabela de regras contida no arquivo pg_hba.conf.
As conexões ao PostgreSQL são estabelecidas via socket. Para fins de autenticação, conexões
do tipo UNIX local (da família AF_UNIX) são classificadas com o tipo local, enquanto
conexões TCP/IP (tanto IPv4, AF_INET, quanto IPv6, AF_INET6) são classificadas como
host. Além dessa informação, é necessário informar um usuário e um banco de dados.
Com esses dados, o PostgreSQL percorre o arquivo pg_hba.conf de cima a baixo, buscando
uma regra de autenticação que seja válida para aquela conexão. A primeira linha encontrada é
usada para decidir qual método de autenticação será aplicado à nova conexão. Isso é análogo
a como tabelas de firewall e roteamento funcionam, e permite a escrita de regras poderosas
pelo uso de máscaras de rede e regras de aceitação e rejeição.
Linhas iniciadas com uma cerquilha (#) são comentários e, portanto, são ignoradas. Outras
linhas seguem uma estrutura de colunas bem definida e separadas por um ou mais espaços.
Formato de um registro do pg_hba.conf:

TYPE DATABASE USER ADDRESS METHOD [OPTIONS]

Campos do pg_hba.conf

TYPE
Primeira coluna. É o tipo de conexão utilizada que pode ser:

• local: conexões através de socket UNIX


• host: conexões por socket TCP
• hostssl: conexões TCP com encriptação SSL
• hostnossl: conexões TCP sem encriptação SSL
• hostgssenc: conexões TCP com encriptação GSSAPI
• hostnogssenc: conexões TCP sem encriptação GSSAPI
109 5. Autenticação e autorização

DATABASE
Segunda coluna. Especifica que nome de base(s) de dados combina(m) com o registro.
É possível especificar mais de uma base de dados no registro, separando os nomes por vírgulas.
Também é possível especificar um arquivo separado que contenha uma lista de nomes de bases
de dados.
O nome desse arquivo deve ser precedido com o caractere “@”.

Há palavras-chaves que podem ser especificadas, que são:

• - Todas bases de dados;


all
• - A base de dados tem que ter o mesmo nome do usuário requisitante;
sameuser
• samerole / samegroup - Força com que o usuário requisitante seja membro de um papel
cujo nome seja idêntico ao da base que quer se conectar.
samegroup - é obsoleto, mas ainda é aceito e tem o mesmo efeito;
• replication - Para fins de replicação via streaming.

USER
Terceira coluna. Especifica que usuário(s) do banco de dados combina(m) com o registro.
Pode-se especificar mais de um nome de usuário, separando-os por vírgulas.
Um arquivo separado que contenha nomes de usuários pode ser especificado. O nome desse
arquivo deve ser precedido por “@”.
O valor all especifica que combina com todos usuários.
Caso contrário, este é o nome de um papel de banco de dados específico, ou um nome de
grupo precedido por “+”.

ADDRESS
Quarta coluna. Quando o tipo de conexão não for local, caso contrário ela não existirá.
Especifica endereços de máquinas clientes que combinem com esse registro.
Este campo pode conter um hostname, endereço IP, endereço de rede ou uma palavra-chave.

Palavras-chave

• all: Qualquer endereço IP;


• samehost: Qualquer IP do host;
• samenet: Qualquer IP da rede que estiver diretamente conectado.
110 5. Autenticação e autorização

METHOD
Define o método de autenticação utilizado.

• trust
Aceita a conexão; útil para testes e laboratórios, mas não recomendado para ambientes
de produção;

• reject
Rejeita a conexão; útil para implementar blacklists de configurações indesejáveis.
• scram-sha-256
Autenticação por senha usando o método de encriptação SCRAM-SHA-256;

• md5
Autenticação por senha usando método de encriptação MD5;

• peer
Aceita a conexão caso o usuário do sistema operacional do cliente tenha o mesmo nome
do usuário informado na conexão com o banco de dados; disponível apenas para conexões
do tipo local;

• ident
Semelhante a peer, mas para conexões dos outros tipos; depende de um servidor ident
confiável na rede local;

• ldap
Autentica usando um servidor LDAP;

• cert
Autentica usando certificados SSL de cliente.

[https://www.postgresql.org/docs/current/auth-methods.html] (https://www.postgresql.org/docs/current/
methods.html)

OPTIONS
Após o campo de método de autenticação, podem ter campos na forma nome = valor que
especificam opções para o método de autenticação.
111 5. Autenticação e autorização

[$] Para os nossos testes, vamos alterar o parâmetro password_encryption para scram-sha-256
e liberar que o PostgreSQL escute em todas as interfaces do sistema operacional:

vim ${PGDATA}/postgresql.conf

listen_addresses = '*'
password_encryption = scram-sha-256

[$] Restart no serviço para aplicar:

pg_ctl restart

[$] Para os testes vamos também fazer um backup do pg_hba.conf original:

cp ${PGDATA}/pg_hba.conf ${PGDATA}/pg_hba.conf.bkp

[$] Alterar a senha para cada role:

cat << EOF | psql -Atq


ALTER ROLE bach PASSWORD '123';
ALTER ROLE beethoven PASSWORD '123';
ALTER ROLE mozart PASSWORD '123';
ALTER ROLE vivaldi PASSWORD '123';
EOF

Criação de regras:

• Liberar conexões locais em qualquer base de dados, provenientes do usuário postgres,


sem a necessidade de autenticação;

• Permitir conexões oriundas da rede 192.168.56.0/24 ao banco db_zero, provenientes de


usuários do grupo dev, mediante autenticação através do SCRAM-SHA-256.
112 5. Autenticação e autorização

[$] Liberar conexões locais em qualquer base de dados para usuário postgres sem senha e
conexões locais ao banco db_zero. Provenientes de usuários do grupo dev devem fornecer
senha:

cat << EOF > ${PGDATA}/pg_hba.conf && pg_ctl reload && clear && cat ${PGDATA}/pg_hba.conf
local all postgres trust
host db_zero +dev 192.168.56.0/24 scram-sha-256
EOF

[$] View pg_hba_file_rules, exibe o conteúdo de pg_hba.conf em forma tabular:

psql -qc 'SELECT * FROM pg_hba_file_rules;'

-[ RECORD 1 ]--------------
line_number | 1
type | local
database | {all}
user_name | {postgres}
address |
netmask |
auth_method | trust
options |
error |
-[ RECORD 2 ]--------------
line_number | 2
type | host
database | {db_zero}
user_name | {+dev}
address | 192.168.56.0
netmask | 255.255.255.0
auth_method | scram-sha-256
options |
error |

[$] Dado que apenas mozart e vivaldi pertencem a dev. Vamos realizar a tentativa de conexão
com o usuário beethoven:

psql -Atqc "SELECT 'Hello, World';" -d db_zero -U beethoven

psql: error: could not connect to server: FATAL: no pg_hba.conf entry for host "[local]", user "beethoven", d

Nenhuma das linhas combinou com os dados de conexão; nenhuma entrada para o usuário
beethoven. . .
113 5. Autenticação e autorização

[$] Testes de conexão com os usuários mozart e vivaldi:

psql -Atqc "SELECT 'Hello, World';" -d db_zero -U mozart -h 192.168.56.2

Password for user mozart:

psql -Atqc "SELECT 'Hello, World';" -d db_zero -U vivaldi -h 192.168.56.2

Password for user vivaldi:

Para cada um, foi pedida senha e, posteriormente, exibida a mensagem de teste “Hello, World”.

[$] Redefinir o pg_hba.conf para aceitar conexões locais (via TCP) provenientes de qualquer
usuário e qualquer banco, mediante autenticação scram-sha-256:

cat << EOF > ${PGDATA}/pg_hba.conf && pg_ctl reload && clear && cat ${PGDATA}/pg_hba.conf
local all postgres trust
host all all 127.0.0.1/32 scram-sha-256
host db_zero +dev 192.168.56.0/24 scram-sha-256
EOF

[$] Testes de conexão com os usuários bach, beethoven, mozart e vivaldi:

psql -Atqc "SELECT 'Hello, World';" -d db_zero -U beethoven -h 192.168.56.2

psql: error: FATAL: no pg_hba.conf entry for host "192.168.56.2", user "beethoven", database "db_zero", SSL o
FATAL: no pg_hba.conf entry for host "192.168.56.2", user "beethoven", database "db_zero", SSL off

psql -Atqc "SELECT 'Hello, World';" -d db_zero -U beethoven -h 127.0.0.1

Hello, World

[$] Restaurar o pg_hba.conf original e reload no serviço:

cat ${PGDATA}/pg_hba.conf.bkp > ${PGDATA}/pg_hba.conf && pg_ctl reload


114 5. Autenticação e autorização
115 5. Autenticação e autorização

O arquivo .pgpass

É a forma mais recomendada para armazenar senhas de acesso ao banco e a utilização do


arquivo .pgpass.
Por padrão, é armazenado na raiz do diretório do usuário com permissões restritas (0600).
Pode-se customizar sua localização / nome do arquivo utilizando a variável de ambiente
PGPASSFILE.
Seu formato é host:port:dbname:user:password. Exemplo:

127.0.0.1:5432:db_one:beethoven:123

O caractere # é utilizado para comentários.


Por comodidade, também é possível substituir qualquer das primeiras quatro colunas por um
asterisco (*) para que a linha seja aplicada independentemente daquele valor. Por exemplo,
como a senha do usuário seria a mesma para todo banco daquela máquina, poderíamos escrever
a regra como:

# host:port:dbname:user:password
127.0.0.1:5432:*:*:123

[$] Criação do arquivo ~/.pgpass:

cat << EOF > ~/.pgpass


# hostname:port:database:username:password
127.0.0.1:5432:*:*:123
EOF

[$] Somente o usuário dono poderá ter acesso ao arquivo:

chmod 0600 ~/.pgpass

[$] Agora, a conexão não pedirá senha devido ao arquivo pgpass:

psql -p 5432 -h 127.0.0.1 -U beethoven -d db_one


116 5. Autenticação e autorização

Arquivo de serviço de conexão

Para simplificar a vida do DBA, o PostgreSQL também permite guardar todas as informações
de conexão em um arquivo simples na raiz do diretório do usuário, chamado .pg_service.conf,
e, então, acessar o banco de dados através de um apelido simples.
Assim como .pgpass, pode-se customizar sua localização / nome do arquivo utilizando a
variável de ambiente PGSERVICEFILE.

[$] Criar o arquivo .pg_service.conf:

cat << EOF >> ~/.pg_service.conf


[meubd]
host=127.0.0.1
port=5432
dbname=postgres
user=postgres
EOF

[$] Então podemos acessá-lo com um comando simplificado, fornecendo apenas o nome do
serviço:

psql service=meubd
117 5. Autenticação e autorização

RLS: Row Level Security - Segurança em nível de linha


O Row Level Security é um recurso disponibilizado a partir da versão 9.5, que complementa o
gerenciamento de permissões do Postgres através de políticas de segurança a nível de linhas
de tabelas.

É preciso habilitar o RLS na tabela desejada com o comando:

ALTER TABLE <tabela> ENABLE ROW LEVEL SECURITY.

O comando CREATE POLICY cria políticas e sua sintaxe é:

CREATE POLICY name ON table_name


[ AS { PERMISSIVE | RESTRICTIVE } ]
[ FOR { `ALL` | SELECT | INSERT | UPDATE | DELETE } ]
[ TO { role_name | PUBLIC | CURRENT_USER | SESSION_USER } [, ...] ]
[ USING ( using_expression ) ]
[ WITH CHECK ( check_expression ) ]

• name
Nome da política;

• table_name
Nome da tabela;

• FOR
Para qual comando DML (SELECT, INSERT, UPDATE, DELETE) a política será usada; (A palavra-
chave ALL representa todos comandos);

• role_name
Papel (grupo / usuário) de aplicação da política;

• USING (using_expression)
Valida acesso a linhas preexistentes;

• WITH CHECK (check_expression)


Valida inserções ou alterações.

[>] Criação do banco de dados de teste:

CREATE DATABASE db_rls;


118 5. Autenticação e autorização

[>] Em ambos os terminais, conecte-se ao banco de dados:

\c db_rls;

[>] Criação de usuários:

CREATE ROLE admin LOGIN PASSWORD '123'; -- Administrador


CREATE ROLE alice LOGIN PASSWORD '123'; -- Usuário comum
CREATE ROLE joana LOGIN PASSWORD '123'; -- Usuário comum

Case I: Tabela de usuários


Neste case, uma tabela de usuários com campos para nome de usuário, hash de senha, nome
real e shell só permitirá a usuários comuns alterarem seus próprios dados, mas ainda assim
poderão ver dados de outros usuários, exceto a coluna de hash de senha, que não estará visível
nem para si mesmo.
O usuário admin pode ver e modificar tudo.

[>] Criação de tabela de testes:

CREATE TABLE tb_usuario(


username text PRIMARY KEY, -- Usuário
pw_hash text, -- Hash de senha
nome_real text NOT NULL, -- Nome
shell text NOT NULL ); -- Shell

[>] Inserir a linha do usuário admin:

INSERT INTO tb_usuario VALUES ('admin', '###', 'Administrador', '/bin/tcsh');


119 5. Autenticação e autorização

[>] Permissões:

-- Usuário admin pode ler, inserir, atualizar e apagar


GRANT SELECT, INSERT, UPDATE, DELETE ON tb_usuario TO admin;

-- Usuários comuns só terão acesso a colunas públicas


GRANT SELECT (username, nome_real, shell) ON tb_usuario TO PUBLIC;

-- Permitir a usuários comuns mudar determinadas colunas


GRANT UPDATE (pw_hash, username, nome_real, shell) ON tb_usuario TO PUBLIC;

[>] Habilitar o recurso de RLS na tabela:

ALTER TABLE tb_usuario ENABLE ROW LEVEL SECURITY;

Terminal 2

[>] Mudar o papel para admin:

\c db_rls admin;

[>] Verificando a tabela:

TABLE tb_usuario;

username | pw | nome_real | shell


----------+----+-----------+-------

Se a tabela tiver o recurso de RLS habilitado, mas nenhuma política definida, por padrão a
política de negação será assumida.
120 5. Autenticação e autorização

Terminal 1

[>] Criar política para o usuário admin:

CREATE POLICY po_admin_all_priv_usuario


ON tb_usuario
TO admin
USING (true)
WITH CHECK (true);

Com true para USING e WITH CHECK, tudo será permitido para o usuário admin.

Terminal 2

[>] Verificando a tabela novamente:

TABLE tb_usuario;

username | pw | nome_real | shell


----------+-----+---------------+-----------
admin | ### | Administrador | /bin/tcsh

Agora, com uma política criada para o role admin, ele consegue enxergar as linhas.

[>] Inserir valores:

INSERT INTO tb_usuario VALUES


('alice', '###', 'Alice', '/bin/bash'),
('joana', '###', 'Joana', '/bin/zsh');

[>] Verificando a tabela após as novas linhas:

TABLE tb_usuario;

username | pw_hash | nome_real | shell


----------+---------+---------------+-----------
admin | ### | Administrador | /bin/tcsh
alice | ### | Alice | /bin/bash
joana | ### | Joana | /bin/zsh
121 5. Autenticação e autorização

[>] Mudar o papel para alice:

\c db_rls alice

[>] Verificar a tabela:

SELECT username, nome_real, shell FROM tb_usuario;

username | nome_real | shell


----------+-----------+-------

Mesmo só selecionando as colunas que lhe são permitidas, não foi possível visualizar qualquer
linha na tabela.
Não há política para o role alice.

Terminal 1

[>] Política para visualização de dados para todos usuários:

CREATE POLICY po_all_view_usuario


ON tb_usuario
FOR SELECT
USING (true);

[>] Usuários normais podem atualizar seus registros, mas limita que shells um usuário normal
pode configurar:

CREATE POLICY po_users_mod_usuario


ON tb_usuario
FOR UPDATE
USING (current_user = username)
WITH CHECK (
current_user = username
AND shell IN (
'/bin/bash',
'/bin/sh',
'/bin/dash',
'/bin/zsh',
'/bin/tcsh')
);
122 5. Autenticação e autorização

Terminal 2

[>] Após ter sido criada uma política para visualização de de dados para todos usuários,
tentar novamente:

SELECT username, nome_real, shell FROM tb_usuario;

[>] Tentativa de alice atualizar uma linha que não lhe pertence:

UPDATE tb_usuario SET nome_real = 'Joana Silva' WHERE username = 'joana';

[>] Tentativa de alice atualizar para um shell que não é permitido:

UPDATE tb_usuario
SET (nome_real, shell) = ('Alice Silva', '/bin/fish')
WHERE username = 'alice';

ERROR: new row violates row-level security policy for table "tb_usuario"

[>] Atualizar para um shell que é permitido e seu nome real:

UPDATE tb_usuario
SET (nome_real, shell) = ('Alice Silva', '/bin/zsh')
WHERE username = 'alice';

[>] Verificando a tabela:

SELECT username, nome_real, shell FROM tb_usuario;

username | nome_real | shell


----------+---------------+-----------
admin | Administrador | /bin/tcsh
joana | Joana | /bin/zsh
alice | Alice Silva | /bin/zsh
123 5. Autenticação e autorização

Terminal 1

[>] Via catálogo listar políticas:

SELECT policyname FROM pg_policies;

policyname
---------------------------
po_admin_all_priv_usuario
po_all_view_usuario
po_users_mod_usuario

[>] Para poder desfazer posteriormente, abrir uma transação:

BEGIN;

[>] Apagando uma política:

DROP POLICY po_admin_all_priv_usuario ON tb_usuario;

Para apagar uma política, é preciso informar em qual tabela foi aplicada.

[>] Apagando a tabela onde foram aplicadas as políticas e verificando o catálogo:

DROP TABLE tb_usuario;


SELECT policyname FROM pg_policies;

Como a tabela das políticas foi apagada, as políticas também deixaram de existir.

[>] Desfazer o bloco de transação:

ROLLBACK;
124 5. Autenticação e autorização

——————– EXTRA ——————–

Case II: Tabela de anotações


Aqui, novamente, o admin pode ver tudo, mas cada usuário só poderá ver suas próprias
anotações.

[>] Criação de tabela:

CREATE TABLE tb_anotacao(


id serial PRIMARY KEY,
username text DEFAULT current_user
REFERENCES tb_usuario (username),
dt timestamptz DEFAULT now(),
title varchar(30),
description text);

[>] Habilitando RLS na tabela:

ALTER TABLE tb_anotacao ENABLE ROW LEVEL SECURITY;

[>] Permissões:

-- Permitir ao usuário admin ler, inserir, atualizar e apagar


GRANT SELECT, INSERT, UPDATE, DELETE ON tb_anotacao TO admin;

-- Em determinadas colunas, permitir INSERT e UPDATE, para todas colunas SELECT


-- e DELETE, para PUBLIC
GRANT
UPDATE (dt, title, description),
INSERT (dt, title, description),
SELECT, DELETE ON tb_anotacao TO PUBLIC;

-- Conceder uso da sequência que está atrelada à tabela


GRANT USAGE ON tb_anotacao_id_seq TO PUBLIC;
125 5. Autenticação e autorização

[>] Política para acesso total ao usuário admin:

CREATE POLICY po_admin_all_priv_anotacao


ON tb_anotacao
FOR ALL
TO admin
USING (true)
WITH CHECK (true);

[>] Somente o próprio usuário pode ler / alterar seus registros:

CREATE POLICY po_users_rw_anotacao


ON tb_anotacao
FOR ALL
TO PUBLIC
USING (current_user = username)
WITH CHECK (current_user = username);

Terminal 2

[>] Usuário joana:

\c db_rls joana;

[>] Inserindo registros:

INSERT INTO tb_anotacao (dt, title, description) VALUES


(now(), 'Teste', 'Primeira anotação da Joana'),
('2020-10-07', 'Segundo Teste', 'Segunda anotação da Joana'),
(now() - '2 days'::interval, 'Título', 'Bla bla bla');

[>] Verificar a tabela:

TABLE tb_anotacao;

id | username | dt | title | description


----+----------+-------------------------------+---------------+----------------------------
1 | joana | 2021-01-22 22:54:53.858684+00 | Teste | Primeira anotação da Joana
2 | joana | 2020-10-07 00:00:00+00 | Segundo Teste | Segunda anotação da Joana
126 5. Autenticação e autorização

3 | joana | 2021-01-20 22:54:53.858684+00 | Título | Bla bla bla

[>] Verificando a tabela como alice:

-- Tornar-se role alice


\c db_rls alice;

-- Listar os registros
TABLE tb_anotacao;

id | username | dt | title | description


----+----------+----+-------+-------------

Nenhum resultado, pois o usuário não tem acesso.

[>] Inserindo registros:

INSERT INTO tb_anotacao (dt, title, description) VALUES


(now(), 'Teste 1', 'Primeira anotação da Alice'),
(now() - '2 weeks'::interval, 'Título', 'Monomonomono');

[>] Verificando a tabela:

TABLE tb_anotacao;

id | username | dt | title | description


----+----------+-------------------------------+---------+----------------------------
4 | alice | 2021-01-22 22:59:21.695411+00 | Teste 1 | Primeira anotação da Alice
5 | alice | 2021-01-08 22:59:21.695411+00 | Título | Monomonomono

[>] Verificando a tabela como admin:

-- Tornar-se role admin


\c db_rls admin;
-- Listar os registros
TABLE tb_anotacao;

id | username | dt | title | description


----+----------+-------------------------------+---------------+----------------------------
1 | joana | 2021-01-22 22:54:53.858684+00 | Teste | Primeira anotação da Joana
2 | joana | 2020-10-07 00:00:00+00 | Segundo Teste | Segunda anotação da Joana
3 | joana | 2021-01-20 22:54:53.858684+00 | Título | Bla bla bla
4 | alice | 2021-01-22 22:59:21.695411+00 | Teste 1 | Primeira anotação da Alice
127 5. Autenticação e autorização

5 | alice | 2021-01-08 22:59:21.695411+00 | Título | Monomonomono

Como esperado, o usuário admin é o único que pode ver todos os registros.
Os outros usuários só podem ver os seus próprios nesta tabela.
6
Instâncias PostgreSQL
• Gerenciamento de instâncias
• Arquivos Físicos e OID

128
129 6. Instâncias PostgreSQL

Gerenciamento de instâncias
Conceito de instância / cluster PostgreSQL

Conforme visto anteriormente, no PostgreSQL, o conceito de cluster de banco de dados, ou


simplesmente “cluster ”, é onde estão os arquivos físicos de um sistema de banco de dados, os
quais contém os objetos, tais como bancos de dados, tabelas etc.
É possível ter mais de um cluster em uma mesma máquina, sendo que para cada um terá um
processo independente, que por sua vez também devem escutar em portas TCP/IP diferentes
entre si. Nesse contexto, cluster significa instância.

initdb

O utilitário initdb tem como objetivo criar clusters (instâncias) de bases de dados PostgreSQL.
Quando executado, ele cria um diretório (PGDATA) contendo os dados iniciais de uma instân-
cia.

Sintaxe:

initdb [opções...] [ --pgdata | -D ] PGDATA

systemd & systemctl

O systemd é o sistema de inicialização (init system) que foi adotado pelas principais dis-
tribuições Linux da atualidade (provavelmente também pela maioria).
Apesar de opiniões diversas sobre, o systemd trouxe melhorias comparando-se aos seus ante-
cessores, como o SysVinit, por exemplo.
Hoje, o trabalho de gerenciamento de serviços no Linux foi muito facilitado graças ao systemd.
O utilitário systemctl é o responsável por gerenciar serviços dentro do systemd.
Para apenas ver o estado do serviço (status), não é preciso ser o usuário root, diferente do
que acontece com enable, disable, start, stop e restart.

[$] Variável de ambiente para versão majoritária do PostgreSQL:

read -p \
'Digite a versão majoritária: ' \
PGMAJOR
130 6. Instâncias PostgreSQL

[$] Listando unit files do systemd e filtrando por postgres e apenas a primeira coluna:

systemctl list-units | fgrep postgres | awk '{print $1}'

postgresql-13.service

[$] Verificando o status do serviço:

systemctl status postgresql-${PGMAJOR}.service

● postgresql-13.service - PostgreSQL database server


Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2021-01-28 16:30:42 -03; 8min ago
Process: 720 ExecStart=/usr/local/pgsql/13/bin/pg_ctl start -D /var/local/pgsql/13/data -s -w -
t 300 (code=exited, status=0/SUCCESS)
Main PID: 745 (postgres)
Tasks: 8 (limit: 1128)
Memory: 37.7M
CGroup: /system.slice/postgresql-13.service
|--751 /usr/local/pgsql/13/bin/postgres -D /var/local/pgsql/13/data
|--771 postgres: logger
|--773 postgres: checkpointer
|--774 postgres: background writer
|--775 postgres: walwriter
|--776 postgres: autovacuum launcher
|--777 postgres: stats collector
`-778 postgres: logical replication launcher

[#] Variável de ambiente para versão majoritária do PostgreSQL (como root):

read -p \
'Digite a versão majoritária: ' \
PGMAJOR

[#] Desabilitando o serviço do PostgreSQL, ou seja, tirando-o da inicialização automática do


servidor:

systemctl disable postgresql-${PGMAJOR}.service

Removed /etc/systemd/system/multi-user.target.wants/postgresql-13.service.
131 6. Instâncias PostgreSQL

[$] Verificar novamente o status do serviço:

systemctl status postgresql-${PGMAJOR}.service

● postgresql-13.service - PostgreSQL database server


Loaded: loaded (/lib/systemd/system/postgresql-13.service; disabled; vendor preset: enabled)
Active: active (running) since Thu 2021-01-28 16:30:42 -03; 12min ago
Main PID: 745 (postgres)
Tasks: 8 (limit: 1128)
Memory: 37.7M
CGroup: /system.slice/postgresql-13.service
|--751 /usr/local/pgsql/13/bin/postgres -D /var/local/pgsql/13/data
|--771 postgres: logger
|--773 postgres: checkpointer
|--774 postgres: background writer
|--775 postgres: walwriter
|--776 postgres: autovacuum launcher
|--777 postgres: stats collector
`-778 postgres: logical replication launcher

Nota-se que, na linha “Loaded”, aparece agora “disabled”, pois o serviço foi desabilitado.

[$] Listando os processos utilizando o padrão (pattern) “postgres”:

pgrep -a postgres

751 /usr/local/pgsql/13/bin/postgres -D /var/local/pgsql/13/data


771 postgres: logger
773 postgres: checkpointer
774 postgres: background writer
775 postgres: walwriter
776 postgres: autovacuum launcher
777 postgres: stats collector
778 postgres: logical replication launcher

[$] Via systemctl, pegar o pid do processo principal do serviço PostgreSQL:

systemctl show --property MainPID --value postgresql-${PGMAJOR}.service

751

[$] Outra forma de achar o pid principal do PostgreSQL:

pgrep -a postgres | fgrep "${PGDATA}" | awk '{print $1}'

751
132 6. Instâncias PostgreSQL

O comando que aponta para o diretório de dados e a primeira coluna.

[#] Reiniciar o serviço do PostgreSQL:

systemctl restart postgresql-${PGMAJOR}.service

Geralmente usado para aplicar determinadas alterações de configuração que exigem que o
serviço seja reiniciado.

[#] Reload no serviço do PostgreSQL:

systemctl reload postgresql-${PGMAJOR}.service

Não causa parada no serviço, faz com que determinadas configurações alteradas sejam apli-
cadas.

[#] Parada no serviço do PostgreSQL:

systemctl stop postgresql-${PGMAJOR}.service

[$] Verificar o status do serviço do PostgreSQL:

systemctl status postgresql-${PGMAJOR}.service

● postgresql-13.service - PostgreSQL database server


Loaded: loaded (/lib/systemd/system/postgresql-13.service; disabled; vendor preset: enabled)
Active: inactive (dead)

Serviço parado: inactive.

[#] Habilitando o serviço e iniciando imediatamente:

systemctl enable --now postgresql-${PGMAJOR}.service

Created symlink /etc/systemd/system/multi-user.target.wants/postgresql-13.service → /lib/systemd/system/postgr


${PGMAJOR}.service.
133 6. Instâncias PostgreSQL

A interessante opção ‘--now‘ faz com que além do serviço ser habilitado ele inicie imediata-
mente.
134 6. Instâncias PostgreSQL

[$] Verificando o status do serviço do PostgreSQL:

systemctl status postgresql-${PGMAJOR}.service

● postgresql-13.service - PostgreSQL database server


Loaded: loaded (/lib/systemd/system/postgresql-13.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2021-01-28 16:45:51 -03; 34s ago
Process: 1107 ExecStart=/usr/local/pgsql/13/bin/pg_ctl start -D /var/local/pgsql/13/data -s -
w -t 300 (code=exited, status=0/SUCCESS)
Main PID: 1110 (postgres)
Tasks: 8 (limit: 1128)
Memory: 18.0M
CGroup: /system.slice/postgresql-13.service
|--1110 /usr/local/pgsql/13/bin/postgres -D /var/local/pgsql/13/data
|--1111 postgres: logger
|--1113 postgres: checkpointer
|--1114 postgres: background writer
|--1115 postgres: walwriter
|--1116 postgres: autovacuum launcher
|--1117 postgres: stats collector
`-1118 postgres: logical replication launcher

Serviço rodando: active.

Utilizando o pg_ctl

O pg_ctl é ferramenta nativa de gerenciamento de clusters de PostgreSQL. Através dele,


podemos iniciar, reiniciar, parar, recarregar e consultar o status de um cluster de PostgreSQL.
Conforme visto anteriormente, seu uso é bem simples, basta indicar o comando e o diretório
onde está o arquivo de configuração postgresql.conf - que em muitos casos está no diretório
da instância (PGDATA). Se a variável de ambiente PGDATA estiver configurada, não é preciso
especificar.
Em distribuições derivadas de Debian, o padrão é
/etc/postgresql/<VERSÃO MAJORITÁRIA>/main/.
Em distribuições derivadas de RedHat, o padrão é
/var/lib/pgsql/<VERSÃO MAJORITÁRIA>/data/, que é o próprio diretório do cluster.

[$] Consultando o status com pg_ctl:

pg_ctl -D /var/local/pgsql/${PGMAJOR}/data status

pg_ctl: server is running (PID: 798)


/usr/local/pgsql/13/bin/postgres "-D" "/var/local/pgsql/13/data"
135 6. Instâncias PostgreSQL

[$] Efetuando um reload com pg_ctl:

pg_ctl -D /var/local/pgsql/${PGMAJOR}/data reload

server signaled

[$] Reiniciando um cluster com pg_ctl:

pg_ctl -D /var/local/pgsql/${PGMAJOR}/data restart

waiting for server to shut down.... done


server stopped
waiting for server to start....2021-05-04 09:49:08.549 -03 [988] LOG: redirecting log output to logging colle
2021-05-04 09:49:08.549 -03 [988] HINT: Future log output will appear in directory "/var/log/pgsql".
done
server started

Modos de parada

Quando um sistema de gerenciamento de banco de dados está em execução, ele gerencia


conexões que podem ter transações ou backups em andamento, assim como gerencia buffers
de escrita em disco (guardados na região de memória compartilhada delimitada pelo parâmetro
shared_buffers).
Então, deve-se decidir, no momento em que o SGBD for parado, o que fazer com as transações
em andamento e com os buffers sujos. Essa decisão influencia diretamente os tempos de parada
e de início posterior.

O PostgreSQL tem três modos de parada, cada um com tratamentos diferentes sobre transações
e buffers:

• smart [s]
– Transações: aguarda o término normal das transações, pelo encerramento das
conexões e pela finalização de backups em andamento
– Buffers: escreve os buffers e deixa a instância em estado consistente.
– Tempos: stop lento e start rápido.
– Sinal: SIGTERM (15)
136 6. Instâncias PostgreSQL

• fast [f] (padrão)


– Transações: aborta transações em andamento, encerra conexões e backups em
andamento.
– Buffers: Escreve os buffers e deixa a instância em estado consistente.
– Tempos: stop médio e start rápido.
– Sinal: SIGINT (2)

• immediate [i]
– Transações: aborta transações em andamento, encerra conexões e backups em
andamento.
– Buffers: descarta buffers sujos e deixa a instância em estado inconsistente, crash-
recovery na próxima vez que o servidor subir através dos logs de transação.
– Tempos: stop rápido e start lento.
– Sinal: SIGQUIT (3)

Como visto acima, cada modo pode ser acionado pelo uso de um sinal diferente enviado ao
postgres/postmaster, o que pode ser feito por meio do comando kill.

Aviso

Nunca tente finalizar o PostgreSQL com um SIGKILL (kill -9).


Esse sinal não permite que o PostgreSQL libere segmentos de memória compartilhada ou
semáforos, além de possivelmente deixar os subprocessos do PostgreSQL no ar.
Pode inclusive causar corrupção de dados.

Mais informações sobre como parar o serviço na documentação oficial:


https://www.postgresql.org/docs/current/server-shutdown.html

[$] Criação de um cluster de teste:

initdb -D /tmp/data -U postgres


137 6. Instâncias PostgreSQL

[$] Modificando o postgresql.conf para que o cluster escute na porta 5433:

sed -i 's/#port = 5432/port = 5433/g' /tmp/data/postgresql.conf

[$] Inicializando o cluster especificando o diretório do cluster:

pg_ctl -D /tmp/data/ start

[$] Testando. . . :

psql -U postgres -p 5433 -c 'SHOW port;'

port
------
5433

[$] Parando o cluster de teste e mudando a variável de ambiente PGDATA para o diretório
do cluster de teste:

pg_ctl -D /tmp/data/ stop && PGDATA='/tmp/data'

[$] Inicializando o novo cluster sem precisar especificar o diretório:

pg_ctl start

[$] Testando e logo em seguida parando o cluster:

psql -U postgres -p 5433 -c 'SHOW port;' && pg_ctl stop

port
------
5433
138 6. Instâncias PostgreSQL

Arquivos Físicos e OID


No PostgreSQL, cada cluster possui diversos arquivos físicos responsáveis pelo armazenamento
de dados, os quais são denominados “filenodes”.

OIDs

OIDs são os identificadores de objetos do PostgreSQL. OIDs são números seriais únicos, por
tipo de objeto, dentro de um cluster.
Bancos de dados, tabelas, índices e tablespaces sempre terão um OID.
Linhas de tabelas terão um OID único em cada tabela, desde que a tabela seja criada com a
opção WITH OIDS.
Embora os OIDs sejam visíveis para usuários e aplicações, não é uma boa prática referir-se
aos objetos pelo OID, deixando a cargo do PostgreSQL administrar esses valores.
A utilidade do OID para o administrador é identificar os objetos dentro do layout físico do
cluster em disco, permitindo algumas ações de manutenção, por exemplo, mover tablespaces
de um diretório para outro ou medir o espaço físico ocupado por um banco de dados ou tabela
(embora o PostgreSQL ofereça funções que retornem esses valores).
Não há utilidade prática para os OIDs de linhas, por isso, a criação de tabelas com OIDs de
linha foi tornada opcional há muitos anos, devendo ser usada para retrocompatibilidade de
sistemas legados.
Para visualizar OIDs, vamos utilizar os catálogos de sistema do PostgreSQL, executando co-
mandos diretamente no psql.

Layout de arquivos do PGDATA

Dentro do diretório de dados do PostgreSQL, também conhecido pelo nome da variável de


ambiente PGDATA, existe uma estrutura interna de arquivos e diretórios que pode variar de acordo
com a versão.
Na documentação oficial online, a descrição dessa estrutura está disponível no seguinte link:

https://www.postgresql.org/docs/current/storage-file-layout.html

Identificando o OID de um banco de dados:

SELECT oid FROM pg_database WHERE datname = '<base de dados>';


139 6. Instâncias PostgreSQL

Identificando o OID de um tablespace:

• Verifique o diretório “pg_tblspc”, onde existirão links para cada um dos tablespaces-
existentes;
• O número do link é o OID do tablespace correspondente.

Identificando o OID de uma tabela:

SELECT oid FROM pg_class WHERE relname = '<tabela>';

Identificando o OID de um índice:

SELECT oid FROM pg_class WHERE relname = '<índice>';

Armazenamento físico

O PostgreSQL armazena os dados em arquivos binários, chamados de “filenodes”.


São arquivos como outros quaisquer, com permissão de acesso exclusivo para o usuário postgres
(podendo ser outro usuário, se o PostgreSQL for iniciado de forma diferente).
Cada tabela ou índice terá pelo menos um arquivo de dados, que pode ser encontrado no
diretório “base” do cluster ou em um tablespace, se o objeto estiver lá.
Se necessário, mais de um arquivo será gerado para o mesmo objeto.
O PostgreSQL trabalha com pedaços de 1GB por padrão, cuja limitação é determinada pelo
parâmetro segment_size, que só pode ser alterado via compilação. O tamanho de arquivos de
1GB é pela limitação de sistemas de arquivos legados.
Os objetos podem ser encontrados no cluster utilizando o seu OID.

Por exemplo, para saber onde estão armazenados os dados do banco “postgres”:

[>] 1) Descubrir o OID do banco:

SELECT oid FROM pg_database WHERE datname='postgres';

. . .
140 6. Instâncias PostgreSQL

[$] 2) Verificar no diretório base:

ls ${PGDATA}/main/base/ -lh

total 124K
drwx------ 2 postgres postgres 4.0K 2013-04-21 18:34 1
drwx------ 2 postgres postgres 4.0K 2011-04-16 00:23 11563
drwx------ 2 postgres postgres 4.0K 2013-04-29 09:30 11564

TOAST (The Oversized-Attribute Storage Technique)

Em português, “A Técnica de Armazenamento de Atributos Superdimensionados”. Tabelas


que contêm colunas com campos suficientemente grandes terão uma tabela associada chamada
TOAST.
Os valores armazenados nessa tabela são campos que são muito grandes para serem mantidos
nos registros da tabela.
Se houver uma tabela TOAST, ela será indicada através do campo reltoastrelid no catálogo
pg_class.
Outro ponto importante é que a relação do nome do arquivo de um filenode com o OID nem
sempre é correta, apesar de, inicialmente, quando um objeto é criado, existir uma relação. O
nome do arquivo pode mudar durante sua vida devido a execução de operações como TRUNCATE
ou VACUUM FULL.
Sabendo disso, a forma mais correta de saber o arquivo referente a um objeto é utilizar
a coluna “relfilenode”, presente no catalogo “pg_class”, que guarda informações sobre os
objetos criados no banco.

O catálogo “pg_class” contém informações dos seguintes tipos de objetos:

c = tipo composto
i = índice
m = view materializada
p = tabela particionada
r = tabela comum
S = sequência
t = tabela TOAST
v = visão
141 6. Instâncias PostgreSQL

[>] ID de uma base de dados:

SELECT oid
FROM pg_database
WHERE datname = 'postgres';

oid
-------
16412

O ID do banco é o nome da pasta que conterá seus objetos.

[>] Criação de tabela de teste:

CREATE TABLE tb_tmp(id serial PRIMARY KEY);

[>] Verificando a estrutura da tabela:

\d tb_tmp

Table "public.tb_tmp"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+------------------------------------
id | integer | | not null | nextval('tb_tmp_id_seq'::regclass)
Indexes:
"tb_tmp_pkey" PRIMARY KEY, btree (id)

Nota-se que, por causa da declaração do campo id, serial é um inteiro com um valor default
atrelado a uma sequence; tb_tmp_id_seq.
Essa tabela também tem um índice; tb_tmp_pkey.
142 6. Instâncias PostgreSQL

[>] Consultando no catálogo pg_class conforme a tabela de teste criada:

SELECT
oid, relfilenode, relname, relkind
FROM pg_class
WHERE relname ~ 'tb_tmp' ORDER BY oid;

oid | relfilenode | relname | relkind


-------+-------------+---------------+---------
16438 | 16438 | tb_tmp_id_seq | S
16440 | 16440 | tb_tmp | r
16444 | 16444 | tb_tmp_pkey | i

ID, arquivo de dados, nome e tipo de relação. Observa-se que até então oid e relfilenode são
iguais.

[$] De posse do oid do banco de dados, acessar seu respectivo diretório:

ls $PGDATA/base/<id da base de dados>

. . . ou utilizando Shell Script para esse fim:

ls $PGDATA/base/`psql -Aqtc \
"SELECT oid FROM pg_database WHERE datname = 'postgres';"`

. . .

Procure os arquivos cujos nomes são os relfilenodes da tabela, da sequence e do índice.

[>] VACUUM FULL na tabela de teste:

VACUUM FULL tb_tmp;


143 6. Instâncias PostgreSQL

[>] Consultando no catálogo pg_class, conforme a tabela de teste criada:

SELECT
oid, relfilenode, relname, relkind
FROM pg_class
WHERE relname ~ 'tb_tmp'
ORDER BY oid;

oid | relfilenode | relname | relkind


-------+-------------+---------------+---------
16438 | 16438 | tb_tmp_id_seq | S
16440 | 16446 | tb_tmp | r
16444 | 16449 | tb_tmp_pkey | i

Observa-se agora que, para a tabela e para o índice, os relfilenodes mudaram.

[>] TRUNCATE da tabela e reiniciando as sequências relacionadas:

TRUNCATE tb_tmp RESTART IDENTITY;

[>] Consultando no catálogo pg_class, conforme a tabela de teste criada:

SELECT
oid, relfilenode, relname, relkind
FROM pg_class
WHERE relname ~ 'tb_tmp'
ORDER BY oid;

oid | relfilenode | relname | relkind


-------+-------------+---------------+---------
16438 | 16452 | tb_tmp_id_seq | S
16440 | 16450 | tb_tmp | r
16444 | 16451 | tb_tmp_pkey | i

Após o TRUNCATE, mudou também o filenode da sequence, por causa da cláusula RESTART
IDENTITY.
144 6. Instâncias PostgreSQL

[$] Para determinar qual é o objeto do arquivo físico:

SELECT
relname "Nome do objeto",
CASE relkind
WHEN 'r' THEN 'tabela comum'
WHEN 'i' THEN 'índice'
WHEN 'S' THEN 'sequência'
WHEN 'v' THEN 'visão'
WHEN 'c' THEN 'tipo composto'
WHEN 't' THEN 'tabela TOAST'
WHEN 'p' THEN 'tabela particionada'
ELSE '---'
END "Tipo de objeto"
FROM pg_class WHERE relfilenode = 16451;

Nome do objeto | Tipo de objeto


----------------+----------------
tb_tmp_pkey | índice
145 6. Instâncias PostgreSQL

Revisao
1. P: Como são referenciados os objetos do PostgreSQL internamente ?
R:
2. P: É uma boa prática habilitar OID para linhas ?
R:
3. P: Tenho o OID de uma tabela, mas não encontro o arquivo fisico. O que pode estar
acontecendo?
R:
4. P: Tenho um tabela onde armazeno arquivos grandes, mas os filenodes referentes a
minha tabela não condizem com o tamanho dos arquivos armazenados. O que está
ocorrendo ?
R:
7
Configuração
• postgresql.conf
• Formas alternativas de configurar parâmetros
• A view pg_settings
• O comando ALTER SYSTEM

146
147 7. Configuração

postgresql.conf
O postgresql.conf é o arquivo principal de configurações do PostgreSQL.
Seu formato consiste em cada linha válida ter parâmetro = valor.
Comentários são feitos pelo caractere sustenido (#).
Há configurações cujos valores são de memória ou tempo com suas respectivas unidades, que
para memória pode ser em kilobytes ou blocos (cada um de 8kb, normalmente) e para tempo
milissegundos, segundos ou minutos.
Os padrões de unidades são encontrados no catálogo pg_settings, no campo unit, tais como:
s, min, kB, 8kB, ms.
Para memória, o multiplicador é 1024 e não 1000.
Caso desejar, pode ser especificada uma unidade diferente de forma explícita.
Exemplos de unidades de memória: kB (kilobytes), MB (megabytes) e GB (gigabytes).
Unidades válidas de tempo são: ms (milissegundos), s (segundos), min (minutos), h (horas)
e d (dias).
Configuração inválidas no postgresql.conf impedem que o serviço inicie / reinicie.
O arquivo de configuração é relido sempre que o processo principal de servidor recebe um sinal
SIGHUP (que é mais facilmente emitido pelo comando no shell pg_ctl reload).
O processo servidor principal também propaga esse sinal para todos os processos atuais que
estejam rodando, de forma que suas sessões existentes vão já trabalhar com o novo valor.
Alternativamente, é possível mandar o sinal para um único processo diretamente.
Alguns parâmetros podem ser configurados apenas na inicialização do servidor; quaisquer
mudanças no arquivo de configuração serão ignoradas até que o servidor seja reiniciado.
Caso um mesmo parâmetro seja declarado mais de uma vez, será considerado seu último valor.

Referências:

• https://www.postgresql.org/docs/current/config-setting.html

• https://www.postgresql.org/docs/current/runtime-config.html

Tipos de valores do postgresql.conf

• bool: valores “booleanos”


Verdadeiro (on, true, 1, yes) ou falso (off, false, 0, no).
• enum: enumeração
É aceito um valor de uma lista permitida do parâmetro.
Os valores válidos estão no campo enumvals do catálogo pg_settings.
Os valores são case sensitive.
• string: texto
148 7. Configuração

Seu valor deve ser envolvido por apóstrofos (’ ’).


• integer: inteiro
Valor numérico inteiro.
• real: valor de ponto flutuante
Valor numérico real, ou seja, são usadas casas decimais, cujo caractere utilizado como
separador de casas decimais é o ponto.

Directivas include

Além dos parâmetros de configuração, o postgresql.conf pode conter directivas de inclusão


(include), que especificam outro arquivo para ler e processar como se seu conteúdo estivesse
inserido naquele ponto.
Essa funcionalidade permite que um arquivo de configuração seja dividido fisicamente em
partes separadas.

Sintaxe:

include = 'nome_do_arquivo'

Se o nome do arquivo não estiver em um caminho absoluto, será tomado como referência o
diretório onde está o arquivo referenciador.
É permitido fazer inclusões aninhadas.
A directiva include_if_exists atua da mesma forma que a directiva include, exceto pelo com-
portamento quando o arquivo referenciado não existe ou não pode ser lido. A directiva include
considera isso uma condição de erro, enquanto include_if_exists apenas adiciona uma men-
sagem de log e continua a processar o arquivo referenciador. Por fim, temos a directiva
include_dir, cuja finalidade é incluir não somente um arquivo de configuração, mas sim um
diretório que lerá todos arquivos com a extensão .conf dentro.

Contextos de parâmetros de configuração

Um contexto de um parâmetro de configuração do PostgreSQL define em que situação ele


pode ou deve ser aplicado, veja tabela abaixo:

Contexto Como as configurações podem ser mudadas


internal Só por compilação ou opções do initdb.
postmaster Só por start ou restart.
sighup Um reload no serviço é suficiente.
backend Pode ser alterada usando a variável de ambiente PGOPTIONS.
superuser-backend Igual a backend, mas somente para superusuário.
149 7. Configuração

Contexto Como as configurações podem ser mudadas


user Pode ser alterada na sessão via comando SET.
superuser Igual a user, mas somente para superusuário.

Exibir / alterar parâmetros

O comando SHOW permite inspecionar o valor atual de todos parâmetros.


A visão (view ) pg_settings também permite exibir e atualizar parâmetros em tempo de exe-
cução; pg_settings é equivalente a SHOW e SET, mas pode ser mais conveniente em usá-la fazendo
junções com outras tabelas, ou qualquer outra consulta que for útil utilizá-la.
Nela também contém mais informações sobre cada parâmetro do que o que está disponível
em SHOW.

SHOW
Sua única função é exibir o valor de uma determinada configuração do PostgreSQL.

[>] Exibir o valor da porta de conexão:

SHOW port;

port
------
5432

SET
Altera qualquer parâmetro de configuração que possa ser mudado dentro de uma sessão, além
de sobrescrever qualquer outro meio de configuração.

[>] Definição de parâmetro dentro de uma sessão:

SET application_name = 'minha_aplicacao';


150 7. Configuração

Formas alternativas de configurar parâmetros


Um outro jeito de definir esses parâmetros de configuração é fornecê-los como opção de linha
de comando do utilitário postgres.
Ao utilizar opções de linha de comando, sobrescrevem as configurações do postgresql.conf.
O que significa não poder alterá-las.
Ocasionalmente útil para uma sessão particular apenas.

[$] Pare a atual instância:

pg_ctl stop

[$] Em um terminal, passe parâmetros de configuração ao daemon:

postgres \
-c log_connections=yes \
-c log_destination='stderr' \
-c port=5433 \
-c log_disconnections=yes \
-c logging_collector=off

O prompt está preso e todas mensagens de log estarão na tela deste terminal.

[$] Conecte em outro terminal e use o psql para se conectar à instância:

psql -p 5433

Agora observe no primeiro terminal que nas mensagens de log apareceu algo como:
2021-01-26 09:40:33.738 -03 [1272] LOG: connection received: host=[local]
2021-01-26 09:40:33.739 -03 [1272] LOG: connection authorized: user=postgres database=postgres application_na

No terminal 2 saia do psql e observe a nova mensagem:


2021-01-26 09:41:49.259 -03 [1272] LOG: disconnection: session time: 0:01:15.521 user=postgres database=postg

Agora, no terminal 1, faça <Ctrl> + C para terminar a instância.


151 7. Configuração

[$] Inicie novamente a instância:

pg_ctl start

[$] A variável de ambiente PGOPTIONS pode ser utilizada para esse propósito no lado do
cliente:

export PGOPTIONS="-c work_mem=12MB"


psql -Atqc 'SHOW work_mem;'

12MB

Isso funciona para qualquer aplicação cliente baseada na libpq, não apenas o psql.
Vale lembrar que só serão aceitos parâmetros ajustáveis em sessões. Além disso, é possível
atribuir um conjunto de configurações de parâmetros para um usuário ou base de dados.
Sempre que uma sessão é iniciada, as configurações padrões para o usuário e base de dados
são carregadas.
Os comandos ALTER ROLE e ALTER DATABASE, respectivamente, são usados para isso.
Configurações por base de dados sobrescrevem tudo vindo de linha de comando ou do
arquivo de configuração. Por sua vez, são substituídas pelas configurações por usuário. Ambas
são substituídas pelas configurações por sessão.
152 7. Configuração

A view pg_settings
É a visão (view ) de sistema que tem informações sobre as configurações do PostgreSQL.
Suas informações vêm da função pg_show_all_settings.

https://www.postgresql.org/docs/current/view-pg-settings.html

[>] Pela view pg_settings, verificar valor, contexto, tipo de valor e unidade:

SELECT
setting, -- Valor
context, -- Contexto
vartype, -- Tipo de dado
unit -- Unidade
FROM pg_settings
WHERE name = 'shared_buffers';

setting | context | vartype | unit


---------+------------+---------+------
16384 | postmaster | integer | 8kB

O valor de shared_buffers exibido aqui é de 16384 páginas de 8 kilobytes (unit). Convertendo


para uma forma mais legível, 128MB.
O tipo de valor é inteiro (integer) e é um parâmetro que seu valor só terá efeito após reinicializar
o serviço do PostgreSQL (postmaster).

[>] Visualizando o parâmetro application_name:

SHOW application_name;

application_name
------------------
psql

[>] Alterando o valor do parâmetro application_name:

SET application_name = 'curso_adm_postgres';


153 7. Configuração

[>] Valor, contexto, tipo de valor e unidade de application_name:

SELECT
setting, context, vartype, unit
FROM pg_settings
WHERE name = 'application_name';

setting | context | vartype | unit


--------------------+---------+---------+------
curso_adm_postgres | user | string |
154 7. Configuração

O comando ALTER SYSTEM


Um dos interessantes recursos vindos com a versão 9.4 foi a possibilidade de podermos alterar
qualquer configuração do PostgreSQL via comando SQL, dispensando a necessidade de editar
o arquivo postgresql.conf.
Um novo arquivo auxiliar foi introduzido nessa versão: o postgresql.auto.conf.
Funciona da seguinte forma: o servidor dará preferência a esse arquivo e as configurações que
nele estiverem declaradas terão prioridade com relação às configurações do postgresql.conf.
O comando ALTER SYSTEM faz as alterações de configurações escritas no postgresql.auto.conf.
A edição direta de postgresql.auto.conf não é recomendada, pois o próprio comando ALTER
SYSTEM o sobrescreve.

Para a parte prática, recomenda-se estar conectado a dois terminais, sendo que o primeiro
dentro do psql e o segundo no shell do sistema operacional como usuário postgres.

[>] Verificando, via SQL, em qual porta o PostgreSQL está escutando:

SHOW port;

port
------
5432

[>] Alterando a porta de escuta do serviço para 5433:

ALTER SYSTEM SET port = 5433;

[$] Reiniciando o serviço no modo fast:

pg_ctl restart -m f

Isso fará com que o terminal onde estava o shell do psql seja desconectado.

[$] Para se reconectar novamente, agora deve especificar a nova porta:

psql -p 5433
155 7. Configuração

[$] Verificando o conteúdo de postgresql.auto.conf:

cat $PGDATA/postgresql.auto.conf

# Do not edit this file manually!


# It will be overwritten by the ALTER SYSTEM command.
port = '5433'

[>] Reset na configuração de port:

ALTER SYSTEM RESET port;

[$] Verificando o conteúdo de postgresql.auto.conf:

cat $PGDATA/postgresql.auto.conf

# Do not edit this file manually!


# It will be overwritten by the ALTER SYSTEM command.

Nota-se que a linha de port foi removida.

Uma vantagem de fazer mudanças utilizando ALTER SYSTEM é a questão da segurança. Se um


parâmetro for configurado com um valor inválido, a mudança não será efetivada. Isso evita
que, em caso de um restart, o servidor não suba devido a uma má configuração.

[>] Tentativa de alterar o parâmetro wal_level com um valor não permitido:

ALTER SYSTEM SET wal_level = 'cold_standby';

ERROR: invalid value for parameter "wal_level": "cold_standby"


HINT: Available values: minimal, replica, logical.
8
Arquitetura e funcionamento
interno do PostgreSQL
• Fundamentos da arquitetura
• Os processos do PostgreSQL
• Shared buffers
• WAL (Write Ahead Log): integridade de dados
• Checkpoint

156
157 8. Arquitetura e funcionamento interno do PostgreSQL

Fundamentos da arquitetura
Uma sessão do PostgreSQL consiste nos seguintes processos (programas):

• backend
Um processo servidor que gerencia os arquivos físicos do banco de dados.
Aceita conexões ao banco, vindas de aplicações clientes, e executa ações em prol desses
clientes. Antigamente, esse aplicativo servidor se chamava postmaster, hoje, é simples-
mente chamado de postgres.

• frontend
A aplicação cliente do usuário que executa operações na base de dados.
Aplicações clientes podem ser de natureza diversificada: um cliente pode ser uma ferra-
menta em modo texto, um aplicativo gráfico, um servidor web (como com PHP, Django,
Ruby On Rails, JBoss, etc), ou uma ferramenta especializada em manutenção de banco
de dados.
Alguns aplicativos clientes são distribuídos com o próprio PostgreSQL.

Normalmente, em aplicações do tipo cliente/servidor, o cliente e o servidor podem estar em


diferentes máquinas. Nesse caso, a comunicação será por uma conexão TCP/IP.
O servidor PostgreSQL pode gerenciar múltiplas conexões simultâneas desses clientes.
Para que isso seja possível, a cada nova conexão, é feito um fork do processo servidor original.
A partir desse ponto, o cliente e um novo processo servidor comunicam sem intervenção do
processo postgres original.
Dessa forma, o processo servidor mestre estará sempre rodando, esperando por conexões
clientes, enquanto que um cliente e seu respectivo processo vem e vão.
Tudo isso é transparente para o usuário.

libpq

A libpq é a API escrita em Linguagem C para o PostgreSQL.


libpq é uma biblioteca que tem um conjunto de funções e permite a aplicações clientes passar
consultas (queries) para o backend servidor do PostgreSQL e receber o resultado dessas con-
sultas.
A libpq é também o motor que está por trás de várias outras interfaces para o PostgreSQL,
incluindo aquelas escritas em C++, Python, Perl, Tcl e ECPG.
Há também vários exemplos completos de aplicações no diretório src/test/examples na dis-
tribuição do código-fonte.
Programas clientes que usam a libpq devem incluir o arquivo de cabeçalho (header) libpq-fe.h
e devem fazer a ligação com a biblioteca libpq.
158 8. Arquitetura e funcionamento interno do PostgreSQL

Blocos / páginas

Os arquivos de dados (data files) e os logs de transação (WAL) do PostgreSQL são divididos
em páginas (blocos), cujo padrão é 8kb, que respectivamente configura-se nos parâmetros
block_size e wal_block_size.
Esses parâmetros são de contexto interno (internal), seus valores só podem ser alterados na
compilação (opções --with-blocksize --with-wal-blocksize do configure).
Tamanhos maiores de blocos de arquivos costumam ser mais efetivos em data warehouses.
Escritas e leituras em disco são feitas por páginas, a não ser quando o parâmetro full_page_writes
estiver desabilitado.
Cada chamada (system call) de fsync (sincronia de arquivos em disco), então, vai trabalhar
com pedaços de 8kb.

Como uma conexão é estabelecida?

O PostgreSQL é implementado usando um simples processo por usuário no modelo clien-


te/servidor. Nesse modelo, há um processo cliente conectado a exatamente um processo
servidor.
Como não sabemos previamente quantas conexões serão feitas, temos que usar o processo
mestre que gera um novo processo servidor toda vez que uma conexão é requerida. Esse
processo mestre é chamado postgres e escuta em uma porta TCP/IP (por padrão a 5432)
para conexões de entrada.
Sempre que uma requisição de conexão é detectada, o processo postgres gera um novo pro-
cesso: um fork. As tarefas do servidor se comunicam entre si utilizando semáforos e memória
compartilhada para garantir integridade de dados a todos acessos de dados simultâneos.
O processo cliente pode ser qualquer programa que entenda o protocolo PostgreSQL.
Muitos clientes são baseados na biblioteca em Linguagem C, a libpq, mas há muitas imple-
mentações independentes, como o driver Java JDBC.
Uma vez que uma conexão é estabelecida, o processo cliente pode enviar uma consulta para
o backend (servidor). A consulta é transmitida usando texto plano, não há análise (parsing)
no frontend (cliente).
O servidor analisa a consulta, cria o plano de execução, executa o plano e retorna as traz as
linhas para o cliente, transmitindo-as em uma conexão estabelecida.
1) A aplicação faz uma requisição de conexão;
2) O processo servidor principal, postgres, também conhecido como processo supervisor, faz
um fork de si mesmo, gerando um backend para atender exclusivamente essa conexão; 3)
A comunicação entre a aplicação cliente e o backend é iniciada; 4) Comunicação (leitura e
escrita) entre backends e o storage manager; 5) Dados em memória, as chamadas “páginas
sujas” (dirty pages) em shared buffers são despejados em disco; o processo de checkpoint;
159 8. Arquitetura e funcionamento interno do PostgreSQL

Fig. 8.1: Como uma conexão é estabelecida

Fluxo de dados em consultas

Após a conexão estabelecida, há a interpretação do comando de busca que vai primeiro na


memória compartilhada.
Se a informação buscada não estiver na memória, essa busca então será feita em disco, o que
devido à questão de diferença de velocidade entre os dois tipos de hardware, levará muito mais
tempo. É muito melhor, em termos de performance, quando a informação está na memória
compartilhada.
160 8. Arquitetura e funcionamento interno do PostgreSQL

Fig. 8.2: Fluxo de dados em consultas

Fluxo de dados em escritas

1) A aplicação faz requisição de conexão ao servidor;


2) O processo supervisor postgres faz um fork de si mesmo gerando um backend;
3) Comunicação estabelecida entre cliente e o backend da conexão. O cliente faz uma
requisição de gravação no banco;
4) A escrita é enviada para o processo walwriter ;
5) O walwriter escreve nos logs de transação (WAL);
6) Os mesmos dados são gravados em memória (shared buffers);
7) Com o checkpoint, as páginas sujas do buffer são enviadas para o processo background
writer (bgwriter );
8) O bgwriter grava os dados nos arquivos físicos (data files) ou também conhecidos como
filenodes;
9) Se o buffer estiver cheio, o backend grava diretamente no armazenamento.
161 8. Arquitetura e funcionamento interno do PostgreSQL
162 8. Arquitetura e funcionamento interno do PostgreSQL

Os processos do PostgreSQL
O PostgreSQL tem, para suas diferentes tarefas, diferentes processos rodando, cada um
cumprindo seu papel.
Esses processos são iniciados e parados pelo processo principal, que é o postgres.
Eles também têm a opção de se anexarem à memória compartilhada e se conectarem a bases
de dados internamente.
Outros processos também podem ser iniciados na hora que o PostgreSQL inicializa pela in-
clusão de um nome de módulo no parâmetro shared_preload_libraries.

[$] Listagem completa de processos buscando pelo padrão “postgres”:

pgrep -a postgres

729 /usr/local/pgsql/13/bin/postgres -D /var/local/pgsql/13/data


738 postgres: logger
741 postgres: checkpointer
742 postgres: background writer
743 postgres: walwriter
744 postgres: autovacuum launcher
745 postgres: stats collector
746 postgres: logical replication launcher

A listagem mostra o processo principal e em seguida seus processos filhos.

postgres

É o nome do processo principal, que cria um novo processo servidor para cada conexão es-
tabelecida e faz um fork de si mesmo. Cada um desses processos servidores chamamos de
backend.
Também conhecido como processo supervisor, gerencia uma coleção de bases de dados de uma
instância, cuja coleção, que é um agrupamento, é chamada de cluster de banco de dados.
O processo supervisor gerencia outros processos servidores (backends), bem como outros tipos
de processos.

archiver

Processo responsável pelo arquivamento de logs de transação (WAL).

autovacuum launcher

Gerencia processos autovacuum workers.


163 8. Arquitetura e funcionamento interno do PostgreSQL

autovacuum worker

Sua função é executar rotinas de manutenção (ANALYZE e VACUUM) automaticamente.


O autovacuum launcher determina a qual base de dados se conectar e executar seu trabalho
nas tabelas.

background writer / bgwriter

É o processo responsável por gravar as páginas sujas (dirty pages) dos shared buffers nos data
files.
Ele tenta fazer com que processos backends não tenham que aguardar por escritas em disco,
pois o background writer fará isso.
O bgwriter fica permanentemente verificando essas páginas sujas nos shared buffers, utilizando
o algoritmo LRU para descartar as menos utilizadas recentemente e, assim, decidir quais blocos
devem ser gravados.
O parâmetro de configuração bgwriter_delay define os intervalos de tempo (em ms), objeti-
vando evitar picos de I/O.
O parâmetro bgwriter_lru_maxpages limita a quantidade máxima de páginas que podem ser es-
critas por rodada. A cada rodada, o bgwriter define a quantidade de páginas a serem gravadas,
baseado na média das paginas limpas em rodadas anteriores junto com um multiplicador que
é definido no parâmetro de configuração bgwriter_lru_multiplier. Esse multiplicador permite
que o número previsto seja igual à média anterior (1.0), menor que a média anterior (< 1.0)
ou maior que a média anterior (> 1.0).
Utilizar o multiplicador protege contra picos de atividade do banco de dados, pois mais buffers
limpos serão deixados no cache.
O número máximo de buffers em bgwriter_lru_maxpages será, então, sempre respeitado.
Valores muito baixos nos parâmetros do background writer reduzem o I/O causado por ele,
mas tornará mais provável que os processos de sessão façam escritas, prejudicando as consultas
dos clientes.
O PostgreSQL foi projetado pensando na utilização do cache de arquivos do sistema opera-
cional (page cache), por isso shared buffers pequenos costumam obter bom desempenho.

checkpointer

Processo especializado em gerenciamento de checkpoints.

stats collector

Processo coletor de estatísticas (armazenadas em catálogos) a respeito de uso de objetos de


bancos de dados.
164 8. Arquitetura e funcionamento interno do PostgreSQL

logger

Gerencia tarefas de logs (registro de atividades de sistema) do PostgreSQL.


Escreve em arquivos de log mensagens de saída de erro, bem como gerencia a rotação desses
arquivos, seja pelo tempo seja pelo tamanho.

walwriter

Gerencia escritas no WAL e evita que processos clientes realizem essa gravação por si mesmos.
Quando são feitas modificações, elas são primeiro escritas nos shared buffers e registros dessas
mudanças vão para o buffer do WAL. As mudanças são gravadas nos segmentos de WAL
quando são efetivadas (committed).
165 8. Arquitetura e funcionamento interno do PostgreSQL

Shared buffers
Shared buffers é a área de memória compartilhada que o PostgreSQL utiliza como buffer /
cache para leitura e escrita.
Seu tamanho é definido pelo parâmetro de configuração shared_buffers, cuja unidade é em
páginas (padrão 8kb). É também possível ajustar de forma mais amigável, utilizando sufixos
de unidades de medidas de dados, e. g. MB, GB.
Ao iniciar o PostgreSQL, esse segmento de memória é totalmente alocado.
Dependendo do mecanismo de memória compartilhada selecionado pelo parâmetro de config-
uração shared_memory_type e em versões mais antigas do PostgreSQL, o sistema operacional
precisará ser configurado para aceitar a quantidade de memória especificada.
Os dados não persistidos em disco (nos data files), que estão nos shared buffers são também
escritos nos logs de transação, também conhecidos como WAL (write ahead log). Por meio
desses é garantida a persistência.

Aviso

Um valor muito alto para o parâmetro shared_buffers pode prejudicar a performance, porque
o bgwriter terá que varrer uma quantidade de blocos muito grande, causando maior demora
de execução e ainda por cima fazendo desperdício de recursos desnecessariamente.

Estado de páginas dos shared buffers

Shared buffers é também uma área de memória dividida em páginas (blocos), as quais refletem
os dados do armazenamento.
As páginas dos shared buffers podem ter dois estados:

• Limpa: página que ainda não sofreu modificação. O que ela contém é igual ao seu
correspondente em disco;

• Suja: Página modificada cujo conteúdo não tem correspondência em disco ainda.

Páginas sujas (dirty pages) posteriormente serão gravadas em disco via checkpoint, por um
backend que necessite gravar em uma página que já está suja ou pelo processo background
writer.
Em termos de performance, o ideal é que essas páginas sujas sejam gravadas por checkpoints.
Já o pior cenário se sucede quando um backend precisar fazer uma gravação (view de sistema
pg_stat_bgwriter, coluna buffers_backend).
166 8. Arquitetura e funcionamento interno do PostgreSQL

Depois de se tornarem sujos, os blocos devem ser gravados em armazenamento, que acontece
em três situações possíveis:

• Por checkpoint: todas as páginas sujas são gravadas em armazenamento a cada check-
point;

• Por um backend: quando um backend precisa gravar em uma página que já está suja;

• Pelo bgwriter : o bgwriter fica constantemente vasculhando os shared buffers através de


algoritmo de LRU, com a finalidade de eliminar páginas sujas menos utilizadas recente-
mente.

Do ponto de vista de desempenho, o ideal é que as páginas sejam gravadas via checkpoints.
Já o pior cenário é quando um backend precisa realizar a gravação.
167 8. Arquitetura e funcionamento interno do PostgreSQL

WAL (Write Ahead Log): integridade de dados


Write-Ahead Log (WAL) é o método padrão para garantir integridade de dados.
Também conhecidos como logs de transação, os arquivos do WAL são arquivos com tamanho
fixo (padrão 16MB) escritos de forma serial. Contêm os mesmos dados de shared buffers que
ainda não foram gravados em armazenamento e sua nomenclatura consistem em 24 dígitos
hexadecimais.
Os arquivos de WAL têm sua nomenclatura com 24 (vinte e quatro) caracteres, dígitos hex-
adecimais que são armazenados na pasta pg_wal, que fica dentro do diretório de dados da
instância (PGDATA). A opção -X do utilitário initdb permite que seja especificado outro di-
retório, então, o que originalmente seria um diretório, passa a ser um link simbólico.
As mudanças nos arquivos de dados (onde tabelas e índices estão) devem ser gravadas somente
depois que tenham sido escritas em log, ou seja, as alterações serão efetivadas fisicamente
(nos arquivos de dados em armazenamento). Assim, é alcançada a integridade de dados.
Caso ocorra um crash, na próxima inicialização do PostgreSQL os arquivos de WAL serão lidos
e aplicados até chegar ao estado consistente.
O parâmetro de configuração max_wal_size determina o tamanho máximo permitido que os
logs de transação cresçam (em tamanho de armazenamento ocupado) entre os checkpoints.
Aumentar esse parâmetro para o funcionamento normal vai melhorar a performance, mas se
houver necessidade de recuperação de uma falha (crash) levará mais tempo também.

Parâmetros WAL

• max_wal_size
Tamanho máximo permitido para crescimento do WAL durante checkpoints automáti-
cos.
Esse limite é flexível e o tamanho do WAL pode exceder max_wal_size em circunstâncias
especiais, como cargas pesadas, quando o archive_command falha, quando há atraso no
consumo de WALs por parte de réplicas em uma replicação via streaming ou devido à
configuração wal_keep_size.
Aumentar o valor de max_wal_size melhora a performance de escrita, porém, em uma
eventual recuperação de crash, vai demorar mais.

• min_wal_size
Com o espaço ocupado de WAL permanecendo abaixo de min_wal_size, os arquivos anti-
gos são sempre reclicados para uso posterior em checkpoint em vez de removidos.
Isso pode ser usado para garantir que haja espaço suficiente para que WAL lide com
picos em seu uso, como pode acontecer ao rodar grandes tarefas em lote.

• wal_keep_size
Tamanho mínimo para manter WALs anteriores (que podem ser reciclados).
168 8. Arquitetura e funcionamento interno do PostgreSQL

Necessário para replicação quando não se utiliza slots de replicação.


169 8. Arquitetura e funcionamento interno do PostgreSQL

Checkpoint
Sobre Checkpoint

Checkpoint é o processo de escrita das páginas sujas (dirty pages) dos shared buffers para os
arquivos físicos (data files).
Em caso de crash de sistema, o procedimento de recuperação verifica o último registro de
checkpoint para determinar o ponto no log (também chamado registro redo) de onde deve
partir a operação REDO.
Quaisquer alterações nos data files antes desse ponto são garantidos já em disco.
Após um checkpoint, os segmentos de log anteriores ao que contém o registro REDO não são
mais necessários e podem ser reciclados ou removidos.
Quando o arquivamento de WAL está pronto, os segmentos de log devem ser arquivados antes
de serem reciclados ou removidos.

Quando acontece um checkpoint?

• Término de um ciclo (tempo) determinado pelo parâmetro de configuração


checkpoint_timeout;

• Esgotamento do limite max_wal_size;

• Comando SQL CHECKPOINT (só para super usuários).

Parâmetros checkpoint

• checkpoint_timeout
Tempo máximo entre checkpoints feitos de forma automática.

• checkpoint_completion_target
É a fração de tempo de checkpoint_timeout para completar a escrita nos data files.
Parâmetro muito útil para evitar picos de I/O, suavizando a escrita.

• checkpoint_flush_after
Se o volume de dados gravados, durante um checkpoint, for maior que seu valor, tenta
forçar o sistema operacional a fazer essas escritas. Então, limita a quantidade de “dirty
data” (dados modificados) no cache de páginas do kernel, reduzindo a probabilidade
de paralisação quando um fsync é feito no final do checkpoint ou quando o sistema
operacional grava dados em lotes maiores em segundo plano.
Pode resultar em uma redução de latência de transação, mas há casos, especialmente
com cargas maiores que shared_buffers, em que poderá degradar a performance, embora
menor do que o cache de página do sistema operacional.
170 8. Arquitetura e funcionamento interno do PostgreSQL

• checkpoint_warning
Escreve uma mensagem no log se os checkpoints por preenchimento de segmentos de
WAL ocorrerem antes de seu valor.
Não serão gerados avisos (warnings) se o checkpoint_timeout for menor que o parâmetro
checkpoint_warning.

Nessa parte prática, serão vistos os WAL sendo reciclados.

[$] Alterar parâmetros no postgresql.conf:

vim ${PGDATA}/postgresql.conf

wal_level = replica

[$] Verificando o contexto da configuração:

psql -Atqc "SELECT context FROM pg_settings WHERE name = 'wal_level';"

postmaster

[$] Devido ao contexto do parâmetro alterado ser postmaster, será preciso reiniciar o serviço:

pg_ctl restart

Em terminais diferentes (chamaremos de X e Y), fazer as seguintes operações:

[$] [X] Loop infinito para monitoramento de arquivos de log do WAL:

while :; do
clear
# Utilizando regex para arquivos
# com 24 caracteres hexadecimais
ls -1 ${PGDATA}/pg_wal | \
awk '/^[0-9,A-F]+$/ {if (length($1) == 24) {print $1} }'
# Cada iteração de loop a cada 3 segundos
sleep 3;
done
171 8. Arquitetura e funcionamento interno do PostgreSQL

[$] [Y] Criação de tabela com muitos registros (também como usuário postgres):

psql -qc "SELECT generate_series(1, 70000000) AS campo1 \


INTO tb_teste_checkpoint;"

[$] [Y] Variável que armazena a quantidade de arquivos de log de transação:

QTD_WAL=`ls -1 ${PGDATA}/pg_wal | \
awk '/^[0-9,A-F]+$/ {if (length($1) == 24) {print $1} }' | wc -l`

[$] [Y] Exibindo o valor da variável:

echo ${QTD_WAL}

64

[$] [Y] Exibindo o valor do parâmetro max_wal_size:

psql -Atqc 'SHOW max_wal_size;'

1GB

[$] [Y] Multiplicando a quantidade de logs de transação por 16:

echo $((${QTD_WAL} * 16))

1024

O resultado da multiplicação de 64 logs de transação x 16 MB resultou em 1024 MB, que é


igual a 1 GB. Isso bate com o valor de max_wal_size.

Garantia de gravação em armazenamento

Para garantir que dados sejam gravados em seu respectivos dispositivos de armazenamento,
para que também não permaneçam somente em cache, há uma chamada de sistema (system
call), fsync.
172 8. Arquitetura e funcionamento interno do PostgreSQL

O retorno de fsync só se dá apenas quando ter finalizado a escrita em armazenamento.


No Postgres, o parâmetro de configuração que habilita ou desabilita tem o mesmo nome:
fsync.
Para uma situação em que seja necessário restaurar um dump em uma base vazia, desabilitar
esse parâmetro provisoriamente trará benefícios de performance. No entanto, após terminar
a restauração, deve habilitá-lo imediatamente para evitar problemas com dados corrompidos,
que podem acontecer por uma parada abrupta, por exemplo.

Método de sincronia de gravação no WAL: wal_sync_method

Com fsync habilitado (e deve estar!!!), é possível escolher o método usado para forçar atual-
izações do WAL em disco (dispositivo de armazenamento) com o parâmetro wal_sync_method.
É um parâmetro cujos valores são enumerados e suas opções são:

• open_datasync:
grava em arquivos WAL com open() e a opção O_DSYNC;
• fdatasync:
chama fdatasync() a cada commit. Padrão no Linux;
• fsync: chama fsync() a cada commit;
• fsync_writethrough: chama fsync() a cada commit, forçando write-through de qualquer
cache de escrita de disco, se for suportado;
• open_sync: grava em arquivos WAL com open() e opção O_SYNC.

Cada opção pode ter sua disponibilidade suportada ou não pelo sistema operacional.
O padrão não é necessariamente o ideal, é preciso mudar essa configuração, testar e comparar
para alcançar uma configuração segura contra falhas e ter um melhor desempenho.
Se for selecionada uma opção não suportada pela plataforma, o serviço não iniciará.

Escritas de página inteiras: full_page_writes

Se uma parada abrupta acontecer durante o momento que uma página estiver sendo gravada,
é provável que após a recuperação do arquivo tenham dados novos e antigos misturados.
O PostgreSQL, para evitar que isso aconteça, faz escritas de páginas completas por vez por
padrão, não importa quantos dados serão gravados.
Para poder recuperar corretamente uma página, é necessário armazená-la por completo em
detrimento do aumento da quantidade de dados gravados no WAL.
Aumentando o intervalo entre os pontos de controle (checkpoints) (parâmetro checkpoint_timeout
), reduz o impacto de I/O.
O parâmetro full_page_writes controla essa característica de gravar ou não páginas inteiras.
Desativando full_page_writes, assim como fsync, também aumento o desempenho de escrita.
Tem um risco menor inclusive, mas não é recomendada sua desativação.
173 8. Arquitetura e funcionamento interno do PostgreSQL

Efetivação síncrona: synchronous_commit

Parâmetro de configuração que controla se a efetivação (commit) vai ser síncrona ou assín-
crona, o que significa que quando uma transação só será efetivada após as escritas em disco
do WAL tiverem terminado (modo síncrono).
Quando o commit é assíncrono, a transação é efetivada antes que haja uma garantia que o
WAL seja gravado.
Em muitas situações, desabilitar synchronous_commit para transações que não sejam críticas
melhora o desempenho de escrita, similar a desligar fsync sem os riscos inerentes de corrupção
de dados.
Se o parâmetro synchronous_standby_names for vazio, as únicas configurações significativas serão
on e off; remote_apply, remote_write and local fazem com que tenham o mesmo nível de sin-
cronização que on.
O comportamento de local para todos modos não-off é aguardar pela escrita local do WAL
em disco.

Resumo de todos os modos:

A) Commit persistente localmente


B) Commit de standby persistente após crash do Postgres
C) Commit de standby persistente após crash do sistema operacional
D) Consistência de consulta em standby

Modo A B C D
remote_apply Sim Sim Sim Sim
on Sim Sim Sim
remote_write Sim Sim
local Sim
off

Tempo de espera de gravação no WAL: wal_writer_delay

É o período de tempo que o walwriter “dorme” após gravar no WAL, exceto se for “acordado”
por uma efetivação assíncrona.

Níveis do WAL: wal_level

Determina quanto de informação será gravada no WAL.


Seu valor padrão é replica, que escreve dados suficientes para suportar arquivamento de WAL
e replicação via streaming, incluindo rodar consultas somente leitura no servidor standby.
O nível minimal tem apenas o suficiente para uma recuperação de uma falha (crash) ou um
174 8. Arquitetura e funcionamento interno do PostgreSQL

immediate shutdown.
E, por último, logical que fornece o necessário para decodificação lógica.

Agrupamento de efetivações: commit_delay e commit_siblings

Em um servidor com muitas transações concorrentes, pove haver uma carga de I/O de ar-
mazenamento muito alta, podendo agrupar várias efetivações (commits) em um único fsync
fazendo uso do “commit atrasado”, que aguarda uma quantidade de tempo (commit_delay) ou
de transações (commit_siblings) antes de fazer o fsync e retornar as efetivações.
Por padrão, commit_delay tem seu valor igual a 0 (zero), ou seja, sem espera, então ajusta-se
para um valor desejado em milissegundos.
O parâmetro commit_siblings, de forma similar, configura a quantidade mínima de transações
simultâneas. Ambos reduzem a carga de I/O de escrita de WAL em servidores com muita
concorrência, mas em servidores com pouca concorrência pode gerar atraso desnecessário e
degradar a performance.

[>] Exibir nome, atual configuração, contexto e valores enumerados dos parâmetros:

SELECT
name, setting, context, enumvals
FROM pg_settings
WHERE name IN (
'fsync',
'full_page_writes',
'synchronous_commit',
'wal_level',
'wal_sync_method',
'wal_writer_delay');

name | setting | context | enumvals


--------------------+-----------+---------+-------------------------------------------
fsync | on | sighup |
full_page_writes | on | sighup |
synchronous_commit | on | user | {local,remote_write,remote_apply,on,off}
wal_sync_method | open_sync | sighup | {fsync,fdatasync,open_sync,open_datasync}
wal_writer_delay | 200 | sighup |
175 8. Arquitetura e funcionamento interno do PostgreSQL

Revisao
1. P: A arquitetura do PostgreSQL é baseada em threads?
R:
2. P: Cite 3 processos essenciais para funcionamento do PostgreSQL?
R:
3. P: Qual o papel do WAL no PostgreSQL?
R:
4. P: O que pode disparar um checkpoint ?
R:
5. P: Qual o tamanho de um segmento de log de transação ?
R:
6. P: Qual o tamanho de uma página no PostgreSQL? ?
R:
7. P: O que são os “shared buffers” ?
R:
8. P: Qual a finalidade do “bgwriter” ?
R:
9
MVCC: Multi Version
Concurrency Control
• Sobre MVCC
• Travas (Locks)

176
177 9. MVCC: Multi Version Concurrency Control

Sobre MVCC
Controle de concorrência de várias versões de transações é a forma que o Postgres utiliza para
garantir o ACID.
Seu funcionamento baseia-se em manter a consistência de uma transação enquanto ela existir,
ainda que outras transações incidam sobre a mesma linha simultaneamente.
É a maneira que o Postgres evita que uma transação que fez uma modificação não propague
para uma transação concorrente.
Alguns outros SGBD, em vez disso, implementam a trava completa de linha (full row lock).
Isso faz com que uma linha modificada por uma transação não possa ser vista por outras
transações até que essa termine, o que não é a implementação correta de ACID.
O PostgreSQL implementa o MVCC utilizando trava compartilhada de linha (shared row
lock). Quando uma linha é modificada por uma transação, essa linha pode ser visível por
outra transação concorrente, mas essa outra transação terá acesso à versão original até que a
transação que está modificando a linha finalize, assim, implementando corretamente ACID.
O MVCC faz com que, ao modificar uma linha, ele efetivamente não altere a linha original.
Em vez disso, cria uma nova linha com as alterações requisitadas, que é uma nova versão.

Identificação de transações (xid), colunas de sistema xmin e xmax

• xid: é a identificação de uma transação. A função txid_current exibe o xid atual;


• xmin: armazena o id de transação (xid) que a linha foi inserida;
• xmax: armazena o id de transação (xid) que a linha foi apagada, lembrando que UPDATEs
também deletam tuplas no Postgres. Se seu valor for zero, indica que a linha nunca
sofreu uma tentativa de UPDATE ou DELETE (que foi dado ROLLBACK).

[>] Criação de tabela de teste:

CREATE TABLE tb_x(campo int);

[>] Verificando a id da transação atual:

SELECT txid_current();

txid_current
--------------
532
178 9. MVCC: Multi Version Concurrency Control

[>] Inserir 3 (três) valores na tabela:

INSERT INTO tb_x (campo)


SELECT generate_series(1, 3);

[>] Verificando a id da transação atual:

SELECT txid_current();

txid_current
--------------
534

[>] Iniciar uma transação nova e verificar sua id:

BEGIN;
SELECT txid_current();

txid_current
--------------
535

Todas operações dentro dessa transação terá esse txid.

[>] Verificar as colunas de sistema xmin e xmax e a coluna campo:

SELECT xmin, xmax, campo FROM tb_x;

xmin | xmax | campo


------+------+-------
533 | 0 | 1
533 | 0 | 2
533 | 0 | 3

Todas as linhas foram inseridas na transação de txid 533.


179 9. MVCC: Multi Version Concurrency Control

[>] Atualização de registro na tabela:

UPDATE tb_x SET campo = 33 WHERE campo = 3;

[>] Verificar as colunas de sistema xmin e xmax e a coluna campo:

SELECT xmin, xmax, campo FROM tb_x;

xmin | xmax | campo


------+------+-------
533 | 0 | 1
533 | 0 | 2
535 | 0 | 33

Agora, a linha modificada tem o xmin igual ao txid da transação corrente.

[>] Remoção de registro na tabela e, em seguida, desfazer com ROLLBACK:

DELETE FROM tb_x WHERE campo = 2;


ROLLBACK;

[>] Verificar as colunas de sistema xmin e xmax e a coluna campo:

SELECT xmin, xmax, campo FROM tb_x;

xmin | xmax | campo


------+------+-------
533 | 0 | 1
533 | 535 | 2
533 | 535 | 3

Nota-se que as linhas de valores 2 e 3 têm xmax diferente de zero.


Isso significa que não são mais “intocadas”, ou seja, foi feita alguma modificação (DELETE ou
UPDATE), cuja transação sofreu um ROLLBACK.

O comportamento acima aparenta ser “estranho”, pois como podemos estar enxergando linhas
com XMAX menor que da transação atual? E por que linhas válidas estão sendo com XMAX
preenchido? Esse comportamento ocorre devido a, nesses cenários, o PostgreSQL, ao invés de
criar um terceira versão da tupla no rollback, ele apenas usa um hint bit (“xmax_rolled_back”),
indicando que o XMAX da tupla original não foi efetivado. Por isso, não deve ser considerado.
180 9. MVCC: Multi Version Concurrency Control

Travas (Locks)
No PostgreSQL, travas (locks) podem ser explícitas, ou seja, requisitadas pela aplicação /
cliente, ou ímplícitas.
Uma trava compartilhada (shared lock) é o tipo de trava que permite que outras transações
possam ler uma linha modificada em uma transação que ainda não finalizou, mas não permite
escrita para essas outras.
Por outro lado, uma trava exclusiva (exclusive lock) é o tipo de trava que não permite nem
sequer leitura enquanto a transação não termina.

Deadlocks

Quando há uma interdependência de liberação de travas entre duas transações concorrentes,


ocorre o que chamamos de deadlock.
Por essa dependência mútua, ambas as transações ficariam aguardando indeterminadamente
e jamais seriam terminadas.
Para evitar que deadlocks mantenham transações detidas, há um tempo limite para terminar
transações que estejam nesse estado. Esse tempo limite é determinado pelo parâmetro de
configuração deadlock_timeout.

Para a parte prática, utilizaremos 3 (três) terminais: A (alpha), B (beta) e Y (gamma).


Os dois primeiros serão a simulação de duas conexões concorrentes e a última para acompan-
hamento (monitoramento) das duas primeiras.
Todos terminais no psql.

[>] (Y) Criação de tabela de teste:

CREATE TABLE tb_foo AS


SELECT generate_series(1, 5)
AS campo;
181 9. MVCC: Multi Version Concurrency Control

[>] (Y) Verificando o oid da tabela:

SELECT oid FROM pg_class WHERE relname = 'tb_foo';

oid
-------
16453

[>] (A) Obtendo o pid da conexão:

SELECT pg_backend_pid();

pg_backend_pid
----------------
959

[>] (B) Obtendo o pid da conexão:

SELECT pg_backend_pid();

pg_backend_pid
----------------
961

[>] (A) Iniciando uma transação e obtendo seu id:

BEGIN;
SELECT txid_current();

txid_current
--------------
551
182 9. MVCC: Multi Version Concurrency Control

[>] (B) Iniciando uma transação e obtendo seu id:

BEGIN;
SELECT txid_current();

txid_current
--------------
552

[>] (Y) Obtendo informações das duas outras conexões:

SELECT
pid, locktype, relation, tuple, transactionid, mode, granted
FROM pg_locks
WHERE pid IN (959, 961)
ORDER BY pid;

pid | locktype | relation | tuple | transactionid | mode | granted


-----+---------------+----------+-------+---------------+---------------+---------
959 | virtualxid | | | | ExclusiveLock | t
959 | transactionid | | | 551 | ExclusiveLock | t
961 | virtualxid | | | | ExclusiveLock | t
961 | transactionid | | | 552 | ExclusiveLock | t

Ambas as conexões com travas exclusivas.

[>] (A) Modificar uma linha e em seguida exibir todas as linhas da tabela na transação:

UPDATE tb_foo SET campo = 0 WHERE campo = 1;


TABLE tb_foo;

campo
-------
2
3
4
5
0
183 9. MVCC: Multi Version Concurrency Control

[>] (Y) Obtendo informações das duas outras conexões:

SELECT
pid, locktype, relation, tuple, transactionid, mode, granted
FROM pg_locks
WHERE pid IN (959, 961)
ORDER BY pid;

pid | locktype | relation | tuple | transactionid | mode | granted


-----+---------------+----------+-------+---------------+------------------+---------
959 | relation | 16453 | | | AccessShareLock | t
959 | relation | 16453 | | | RowExclusiveLock | t
959 | virtualxid | | | | ExclusiveLock | t
959 | transactionid | | | 551 | ExclusiveLock | t
961 | virtualxid | | | | ExclusiveLock | t
961 | transactionid | | | 552 | ExclusiveLock | t

Agora, podemos notar que o oid da tabela foi relacionado.


Nota-se que há uma trava exclusiva de linha (RowExclusiveLock).

[>] (B) Modificar uma linha e, em seguida, exibir todas as linhas da tabela na transação:

UPDATE tb_foo SET campo = 33 WHERE campo = 3;


TABLE tb_foo;

campo
-------
1
2
4
5
33
184 9. MVCC: Multi Version Concurrency Control

[>] (Y) Obtendo informações das duas outras conexões:

SELECT
pid, locktype, relation, tuple, transactionid, mode, granted
FROM pg_locks
WHERE pid IN (959, 961)
ORDER BY pid;

pid | locktype | relation | tuple | transactionid | mode | granted


-----+---------------+----------+-------+---------------+------------------+---------
959 | relation | 16453 | | | RowExclusiveLock | t
959 | virtualxid | | | | ExclusiveLock | t
959 | transactionid | | | 551 | ExclusiveLock | t
959 | relation | 16453 | | | AccessShareLock | t
961 | transactionid | | | 552 | ExclusiveLock | t
961 | relation | 16453 | | | RowExclusiveLock | t
961 | virtualxid | | | | ExclusiveLock | t
961 | relation | 16453 | | | AccessShareLock | t

Em ambas as conexões, foram feitas modificações que ainda não têm uma linha em comum.

[>] (B) Modificar a mesma linha que foi modificada na outra transação concorrente:

UPDATE tb_foo SET campo = 11 WHERE campo = 1;

Transação B obteve trava compartilhada na tupla de id 3 e aguarda obter a trava compartilhada


na tupla de id 1.

[>] (Y) Obtendo informações das duas outras conexões:

SELECT
pid, locktype, relation, tuple, transactionid, mode, granted
FROM pg_locks
WHERE pid IN (959, 961)
ORDER BY pid;

pid | locktype | relation | tuple | transactionid | mode | granted


-----+---------------+----------+-------+---------------+------------------+---------
959 | transactionid | | | 551 | ExclusiveLock | t
959 | relation | 16453 | | | AccessShareLock | t
959 | relation | 16453 | | | RowExclusiveLock | t
959 | virtualxid | | | | ExclusiveLock | t
961 | tuple | 16453 | 1 | | ExclusiveLock | t
961 | relation | 16453 | | | AccessShareLock | t
961 | transactionid | | | 552 | ExclusiveLock | t
961 | relation | 16453 | | | RowExclusiveLock | t
961 | virtualxid | | | | ExclusiveLock | t
961 | transactionid | | | 551 | ShareLock | f
185 9. MVCC: Multi Version Concurrency Control

Na última linha, indica que há uma espera de trava (granted = f ).

[>] (A) Aqui também vai ser feita uma tentativa de modificação de uma linha que outra
transação concorrente está modificando:

UPDATE tb_foo SET campo = 333 WHERE campo = 3;

ERROR: deadlock detected


DETAIL: Process 959 waits for ShareLock on transaction 552; blocked by process 961.
Process 961 waits for ShareLock on transaction 551; blocked by process 959.
HINT: See server log for query details.
CONTEXT: while updating tuple (0,3) in relation "tb_foo"

Ocorreu um deadlock, onde a transação B estava aguardando para obter a trava compartilhada
na linha de valor 1 (que pertencia à transação A) e a transação A esperando para ter a trava
compartilhada da linha de valor 3 (que por sua vez pertence à transação B).

Nota-se que há um conflito, o deadlock, após sua detecção finaliza, a transação, o que significa
que todas suas operações foram perdidas.
Existe um parâmetro de configuração denominado log_lock_waits, que ao ser habilitado registra
em log quando uma transação estiver esperando para obter uma trava a um tempo maior que
deadlock_timeout.

Isso é muito útil para verificar problemas de performance causados por deadlocks.

[>] (Y) Obtendo informações das duas outras conexões:

SELECT
pid, locktype, relation, tuple, transactionid, mode, granted
FROM pg_locks
WHERE pid IN (959, 961)
ORDER BY pid;

pid | locktype | relation | tuple | transactionid | mode | granted


-----+---------------+----------+-------+---------------+------------------+---------
961 | relation | 16453 | | | AccessShareLock | t
961 | relation | 16453 | | | RowExclusiveLock | t
961 | virtualxid | | | | ExclusiveLock | t
961 | transactionid | | | 552 | ExclusiveLock | t
186 9. MVCC: Multi Version Concurrency Control

[>] (B) Exibir todas as linhas da tabela:

TABLE tb_foo;

campo
-------
2
4
5
33
11

[>] (B) Efetivar a transação:

COMMIT;
10
Rotinas de manutenção
• Vacuum
• Analyze
• Autovacuum
• Vacuum freeze

187
188 10. Rotinas de manutenção

Vacuum
No modelo MVCC, certas operações criam tuplas mortas em tabelas. Então, é preciso conter
o acúmulo dessas, pois ocupam espaço e fazem com que a tabela tenha um tamanho maior
que efetivamente tem em dados (tuplas vivas). A existência de tuplas mortas em uma tabela
é conhecida como inchaço ou, em inglês, bloat.
Operações que geram tuplas mortas:

• DELETE;
• UPDATE;
• INSERT (apenas quando uma transação é cancelada: ROLLBACK).

Para resolver essa questão, o PostgreSQL disponibiliza um mecanismo de vacumização, que é


mais conhecido como vacuum.

Princípios básicos de vacumização

O comando VACUUM do PostgreSQL é utilizado para vacumização manual, ou seja, feito de uma
forma não automatizada, como é o processo de autovacuum, que será explicado mais adiante.
O comando VACUUM tem que processar cada tabela em uma base pelos seguintes motivos:

• Recuperar / reusar espaço em disco ocupado por tuplas mortas;

• Atualizar dados estatísticos usados pelo planejador de consultas;

• Atualizar o mapa de visibilidade, aumentando a velocidade de index-only scans;

• Proteger contra perdas de dados muito antigos devido a transaction ID wraparound.

Em termos de economia de espaço em disco, há duas variantes principais de vacumização:


vacumização padrão (comando VACUUM) e vacumização completa (comando VACUUM FULL).
A vacumização padrão marca as tuplas mortas como reutilizáveis e, então, para futuras in-
serções de dados, em vez de serem feitas no final da tabela, aproveita-se esses espaços, o que
é até mais “barato”. Ou seja, reaproveitar tuplas consome menos recursos do que criar uma
nova linha.
A vacumização padrão pode rodar em paralelo com operações de produção do banco, pois não
causa lock. A vacumização completa recupera ainda mais espaço, pois é um processo mais
agressivo, tem I/O mais intensos, demora mais e causa lock na tabela. Nessa abordagem, a
tabela é copiada de forma otimizada (sem tuplas mortas).
189 10. Rotinas de manutenção

Para explicar melhor, seguem dois cases ilustrados:

Vacumização padrão

• A) A tabela foi criada e, logo em seguida, recebeu 10 registros;

• B) A tabela sofreu um UPDATE na linha 2 e dois DELETEs nas linhas 6 e 8;

• C) Foi feita a vacumização padrão na tabela e suas tuplas mortas (vermelho) foram
remarcadas como reutilizáveis (azul);

• D) Após a vacumização padrão, a tabela recebe mais dois registros via INSERT, reaprovei-
tando tuplas que estavam marcadas para reutilização. Ainda sobrou uma tupla
marcada como reutilizável para um futuro INSERT.
190 10. Rotinas de manutenção

Vacumização completa

• A) A tabela foi criada e, logo em seguida, recebeu 10 registros;

• B) A tabela sofreu um UPDATE na linha 2 e dois DELETEs nas linhas 6 e 8;

• C) Foi feita a vacumização completa na tabela e, então, as tuplas mortas foram


eliminadas e o tamanho foi reduzido;

• D) Após a vacumização completa, a tabela recebe mais dois registros via INSERT e
esses foram para o final.

Mapa de visibilidade

É um recurso que foi introduzido a partir da versão 8.4 do PostgreSQL.


Em inglês, visibility map é conhecido pela sigla VM e seu arquivo segue o padrão relfilen-
ode_vm. Por exemplo, uma tabela cujo relfilenode é 12345, terá seu arquivo de mapa de
visibilidade igual a 12345_vm.
O mapa de visibilidade tem informações de quais páginas contêm apenas tuplas que sabe-se
estarem visíveis para transações ativas.
Também guarda informações de que páginas contêm apenas tuplas congeladas (frozen tuples).
Em cada heap page sua são armazenados dois bits de controle. O primeiro indica que uma
página aponta para tuplas totalmente visíveis, que não tem qualquer tupla que precisam sofrer
um processo de vacuum. Essa informação pode ser usada para buscas exclusivas por índices
(index-only scans). O segundo bit indica se todas as tuplas na página foram congeladas.
191 10. Rotinas de manutenção

Sendo assim, mesmo um vacuum anti-wraparound não precisa revisitar a página.

Atualizando o mapa de visibilidade

A vacumização mantém um mapa de visibilidade pra cada tabela para manter o controle de
que páginas contêm apenas tuplas que devem ser visíveis para todas transações ativas (e todas
transações futuras, até a página ser novamente modificada).
Isso tem dois propósitos. Primeiro, a vacumização por si só pode pular tais páginas na próxima
vez que rodar, desde que não tenha nada para limpar. Segundo, permite ao PostgreSQL
responder algumas consultas usando apenas índices, sem referenciar a tabela subjacente.
Desde que índices PostgreSQL não contenham informação de visibilidade de tuplas, uma
pesquisa normal de índice busca a pilha de tupla para cada entrada de índice correspondente,
para verificar se deve ser visto pela transação atual. Uma busca index-only, por outro lado,
verifica o mapa de visibilidade primeiro.
Se é todas as tuplas na página são visíveis, a busca em pilha pode ser ignorada.
Isso é mais perceptível em grandes conjuntos de dados, onde o mapa de visibilidade pode
impedir os acessos ao disco.
O mapa de visibilidade é muito menor do que a pilha, de modo que pode ser facilmente
armazenado em cache, mesmo quando a pilha é muito grande.

Prevenindo falhas de id de transações envolventes (transaction id wraparound )

A semântica de transação MVCC do PostgreSQL depende de ser capaz de comparar números


de ID de transação (XID): uma versão de linha com uma inserção XID maior do que a atual
XID de transação está “no futuro” e não deve ser visível para a transação atual.
Uma vez que IDs de transação tem tamanho limitado (32 bits), entretanto, um cluster que
roda por um longo tempo (mais do que 4 bilhões de transações) sofreria com a ID de transação
envolvente: o contador de XID envolve em torno de zero e repentinamente transações que
estavam no passado parecem estar no futuro; o que significa que sua saída pode se tornar
invisível.
Os dados ainda estão lá, mas não é muito confortante se não conseguir acessá-los.
Para prevenir isso, é necessário vacumizar cada tabela, em cada banco de dados, pelo menos
uma vez a cada dois bilhões de transações.
A razão pela qual a vacumização periódica resolve o problema é que o PostgreSQL reserva
uma XID especial como FrozenXID (XID congelada).
Essa XID não segue a comparação de XID normal e é sempre considerada mais antiga do que
toda XID normal.
XIDs normais são comparadas usando aritmética módulo 2ˆ32. Isso significa que para toda
XID normal, há dois bilhões de XIDs que são mais “antigas” e dois bilhões que são “mais
novas”; outra maneira de dizer que o espaço de uma XID normal é circular, sem ponto final.
Portanto, uma vez que uma versão de linha tenha sido criada com uma XID normal particular,
a versão de linha parecerá estar “no passado” para os próximos dois bilhões de transações,
192 10. Rotinas de manutenção

não importa que XID normal está se falando sobre.


Se a versão da linha ainda existe após mais do que dois bilhões de transações, ela parecerá de
repente no “futuro”.
Para evitar isso, as versões antigas de linhas devem ser transferidas a XID FrozenXID algum
tempo antes que eles atinjam o marca de dois bilhões de transações de idade.
Uma vez que essas XIDs especiais são atribuídas, parecem estar “no passado” para todas as
operações normais, independente de questões correlatas. Então, essas versões de linha serão
válidas até serem apagadas, não importa quanto tempo é isso.
Essa reassimilação de antigas XIDs é controlado pelo vacuum.

Fig. 10.1: Ciclo de transações

Freeze

Como já visto, no modelo MVCC o Postgres utiliza a id de transação (xid) para definir a
visibilidade das linhas.
Essa id de transação é um número inteiro de 32 bits, que limita a 4 bilhões de transações até
zerar.
Zerando essas transações, pode causar transtornos, pois indicaria que todas as linhas estariam
no futuro e não estariam mais visíveis.
193 10. Rotinas de manutenção

Para evitar isso, antecedendo o limite alcançado, o vacuum marca as tuplas com uma xid
especial chamada FroozenXID, indicando que essa tupla está no passado.
Ao executar o VACUUM, todas tuplas cuja idade (em número de transações) maior que o
parâmetro vacuum_freeze_min_age terão suas xids substituídas pelo FroozenXID.
Por padrão, o vacuum apenas analisa páginas onde existam tuplas mortas, mas é preciso que
a análise da tabela seja completa quando alcança uma determinada idade, cuja definição está
no parâmetro de configuração vacuum_freeze_table_age.
O parâmetro autovacuum_freeze_max_age define o limite de idade para execução de vacuum em
uma tabela, mesmo que o autovacuum esteja desabilitado.

O comando VACUUM

Coleta de lixo (garbage collect) e opcionalmente analisa uma base de dados.


Sem parâmetros, processa cada tabela no banco de dados atual que o usuário tiver permissão.
Com um parâmetro, o VACUUM interpreta como nome de tabela e processa apenas ela.
Quando a lista de opções é envolvida por parênteses, as opções podem ser escritas em qualquer
ordem.

https://www.postgresql.org/docs/current/sql-vacuum.html

Sintaxe:

VACUUM [ ( option [, ...] ) ] [ table_and_columns [, ...] ]


VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ table_and_columns [, ...] ]

where option can be one of:

FULL [ boolean ]
FREEZE [ boolean ]
VERBOSE [ boolean ]
ANALYZE [ boolean ]
DISABLE_PAGE_SKIPPING [ boolean ]
SKIP_LOCKED [ boolean ]
INDEX_CLEANUP [ boolean ]
TRUNCATE [ boolean ]
PARALLEL integer

and table_and_columns is:

table_name [ ( column_name [, ...] ) ]

• FULL
Vacumização “completa”, recupera espaço, recria índices, mas leva mais tempo e exige
trava exclusiva de tabela. Precisa de espaço extra, pois é feita uma nova cópia da
tabela e não libera a cópia antiga até que a operação termine. Deve apenas ser usado
194 10. Rotinas de manutenção

quando uma quantidade significante de espaço precisa ser recuperada internamente em


uma tabela.

• FREEZE
Faz a vacumização com “congelamento” agressivo de tuplas.
Equivalente a realizar a vacumização com o parâmetro. vacuum_freeze_min_age = 0;

• VERBOSE
Modo verboso;

• ANALYZE
Atualiza estatísticas usadas pelo planejador para determinar o caminho mais eficiente
para executar uma consulta;

• DISABLE_PAGE_SKIPPING
Desabilita ignorar páginas para fins de visibilidade.
Utilizar esta opção em casos de suspeita do conteúdo do mapa de visiblidade;

• SKIP_LOCKED
Faz com que não aguarde por qualquer trava conflitante seja liberada. Se isso acontecer,
a relação será ignorada, e o VACUUM passará para a próxima;

• INDEX_CLEANUP
Remoção de entradas de índices que apontam para tuplas mortas;

• TRUNCATE
Trunca todas as páginas vazias no fim da tabela, liberando espaço;

• PARALLEL
Executa as fases de VACUUM de limpeza de índices em paralelo, usando um número inteiro
de background workers;

• table_name
O nome (opcionalmente qualificado de esquema) de uma tabela específica para vacumiza-
ção;

• column_name
O nome de uma coluna específica para analisar. Por padrão, todas as colunas. Se uma
lista de colunas for especificada, ANALYZE está implícito.
195 10. Rotinas de manutenção

Para evitar picos de I/O durante a execução do vacuum, é possível definir um custo máximo
através do parâmetro de configuração vacuum_cost_limit e um período de pausa definido no
parâmetro de configuração vacuum_cost_delay.
Esse custo é baseado nos valores das seguintes opções:

• vacuum_cost_page_hit
Custo estimado para fazer limpeza de um buffer encontrado no cache.
Representa o custo para travar o pool, procurar a tabela hash e varrer o conteúdo da
página;

• vacuum_cost_page_miss
Similar ao item anterior, mas para buffers que terão de ser lidos em disco;

• vacuum_cost_page_dirty
Similar ao item vacuum_cost_page_hit, mas para páginas que foram modificadas, pois
existirá um I/O extra para despejar o buffer em disco.

• maintenance_work_mem
Define o limite de memória utilizado para cada operação. Caso o valor seja excedido, o
processo continuará com arquivos temporários.
• max_worker_processes
Número máximo de processos background que o sistema suportará.
Ao mudar este parâmetro, é preciso considerar também ajustar os parâmetros
max_parallel_workers, max_parallel_maintenance_workers e
max_parallel_workers_per_gather.

• max_parallel_workers_per_gather
Número máximo de workers que podem ser iniciados por um único Gather ou Gather
Merge node.
Se este parâmetro for denido como zero, desabilita consultas paralelas.

• max_parallel_workers
Número máximo de workers que o sistema pode suportar para operações paralelas.
Ao mudar seu valor, deve-se considerar alterar também max_parallel_maintenance_workers
e max_parallel_workers_per_gather.
Se mudar seu valor para maior que max_worker_processes, não terá efeito, pois workers
paralelos vêm do pool de workers estabelecido nesse parâmetro.

• max_parallel_maintenance_workers
O processo de vacuum pode ser paralelizado por meio da opção PARALLEL, que permite que
uma rotina de vacuum possa ser executada de forma mais rápida, distribuindo trabalho
196 10. Rotinas de manutenção

para mais de uma CPU.


Esse parâmetro define o número máximo de workers paralelos, cujo valor deve ser menor
que max_parallel_workers e max_worker_processes.
197 10. Rotinas de manutenção

[>] Criação de tabela de teste:

CREATE TABLE tb_vacuum AS


SELECT
generate_series(1, 1000000) AS campo1,
generate_series(1, 1000000) AS campo2;

[>] Verificar oid e relfilenode da tabela:

SELECT oid, relfilenode FROM pg_class WHERE relname = 'tb_vacuum';

oid | relfilenode
-------+-------------
24750 | 24750

Por enquanto, o oid é igual ao relfilenode.

[>] Criação de índices para a tabela:

CREATE INDEX idx_campo1 ON tb_vacuum (campo1);


CREATE INDEX idx_campo2 ON tb_vacuum (campo2);

[>] Apagando valores da tabela:

DELETE FROM tb_vacuum


WHERE campo1 > 700000
AND campo2 > 700000;

Note que as linhas apagadas estão no final da tabela.

[>] Verificando o tamanho da tabela:

SELECT pg_size_pretty(pg_relation_size('tb_vacuum'));

pg_size_pretty
----------------
35 MB
198 10. Rotinas de manutenção

[>] Execução de VACUUM com TRUNCATE, paralelo com 2 workers e modo verboso:

VACUUM (TRUNCATE, PARALLEL 2, VERBOSE) tb_vacuum;

. . .

[>] Verificar oid e relfilenode da tabela:

SELECT oid, relfilenode FROM pg_class WHERE relname = 'tb_vacuum';

oid | relfilenode
-------+-------------
24750 | 24750

Ainda iguais. . .

[>] Verificando o tamanho da tabela:

SELECT pg_size_pretty(pg_relation_size('tb_vacuum'));

pg_size_pretty
----------------
24 MB

Após a execução do VACUUM com TRUNCATE, as tuplas mortas que estavam no final foram elimi-
nadas, resultando em uma tabela menor.

[>] Atualizando valores da tabela:

UPDATE tb_vacuum SET campo1 = campo1 + 1 WHERE campo1 < 700000;


UPDATE tb_vacuum SET campo2 = campo2 + 1 WHERE campo2 < 700000;

[>] Verificando o tamanho da tabela:

SELECT pg_size_pretty(pg_relation_size('tb_vacuum'));

pg_size_pretty
----------------
73 MB
199 10. Rotinas de manutenção

Por causa do inchaço causado pelas modificações, a tabela cresceu consideravelmente.

[>] Execução de VACUUM FULL com ANALYZE e modo verboso:

VACUUM (FULL, ANALYZE, VERBOSE) tb_vacuum;

. . .

[>] Verificando o tamanho da tabela:

SELECT pg_size_pretty(pg_relation_size('tb_vacuum'));

pg_size_pretty
----------------
24 MB

A tabela foi totalmente reescrita pelo VACUUM FULL, resultando em seu tamanho como uma
tabela desfragmentada.

[>] Verificar oid e relfilenode da tabela:

SELECT oid, relfilenode FROM pg_class WHERE relname = 'tb_vacuum';

oid | relfilenode
-------+-------------
24750 | 24755

Agora, notamos que o relfilenode mudou, pois com o VACUUM FULL a tabela foi totalmente ree-
scrita criando um novo filenode e apagando o antigo ao término da operação.
200 10. Rotinas de manutenção

Analyze
O comando ANALYZE faz estatísticas sobre o conteúdo de tabelas na base de dados e armazena
os resultados no catálogo de sistema pg_statistic.
Subsequentemente, o planejador de consultas usa essas estatísticas para determinar o mais
eficiente plano de execução.
Sem parâmetros, ANALYZE examina todas as tabelas na base de dados atual.
Com um parâmetro, examina apenas aquela tabela.
Há também a possibilidade de fornecer uma lista de nomes de colunas. Nesse caso, as estatís-
ticas serão coletadas apenas para elas.
O parâmetro default_statistics_target define a quantidade de linhas de amostras ao coletar
para estatísticas por tabela. Essa quantidade é seu valor multiplicado por 300, sendo seu valor
padrão 100, então são 30000 (trinta mil) linhas de amostra por padrão.
Quanto maior seu valor, aumentará o tempo necessário para executar o ANALYZE, mas em
compensação aumentará a qualidade de estimativa do planejador de consultas.

Atualizando o planejador de estatísticas

O planejador de consultas depende de informações estatísticas sobre conteúdos de tabelas para


gerar bons planos de consultas.
Essas estatísticas são coletadas pelo comando ANALYZE, que pode ser invocado por si só ou
como uma etapa opcional no comando VACUUM.
É importante ter estatísticas razoáveis, caso contrário, escolhas pobres de planos podem
degradar o desempenho do banco de dados.
O daemon autovacuum, se habilitado, automaticamente envia comandos ANALYZE sempre que
o conteúdo de uma tabela tiver mudado suficientemente.
Administradores podem preferir depender de operações ANALYZE manualmente agendadas, espe-
cialmente se é sabido que a atividade de atualização em uma tabela não afetará as estatísticas
de colunas “interessantes”.

Aviso

Sempre que for alterada significativamente a distribuição dos dados dentro de uma tabela,
rodar o comando ANALYZE é fortemente recomendado.
ANALYZE (ou VACUUM ANALYZE) atualiza as estatísticas da tabela.
Sem estatísticas obsoletas, o planejador de consultas toma decisões ruins, causando redução
de performance.
Se o daemon autovacuum estiver habilitado, ele deve fazer o ANALYZE automaticamente.
201 10. Rotinas de manutenção

[>] Definindo default_statistics_target = 1; 300 amostras:

SET default_statistics_target = 1;

[>] Criação de tabela de teste com um milhão de registros:

CREATE TABLE tb_teste_stat


AS SELECT (random() * 1000000)::int AS campo
FROM generate_series(1, 10000000);

[>] Verificando no catálogo pg_stats se existe algum registro para a tabela criada:

SELECT count(*) FROM pg_stats WHERE tablename = 'tb_teste_stat';

count
-------
0

[>] Execução de ANALYZE de forma verbosa:

ANALYZE VERBOSE tb_teste_stat;

. . . 300 rows in sample, 10000048 estimated total rows

Como previsto, 300 linhas de amostra (300 rows in sample).

[>] Definindo default_statistics_target = 2; 600 amostras:

SET default_statistics_target = 2;

[>] Execução de ANALYZE de forma verbosa:

ANALYZE VERBOSE tb_teste_stat;

. . . 600 rows in sample, 10000048 estimated total rows


202 10. Rotinas de manutenção

600 linhas de amostra (600 rows in sample).

[>] Verificando no catálogo pg_stats se existe algum registro para a tabela criada:

SELECT count(*) FROM pg_stats WHERE tablename = 'tb_teste_stat';

count
-------
1

[>] Definindo default_statistics_target = 10; 3000 amostras:

SET default_statistics_target = 10;

[>] Execução de ANALYZE de forma verbosa:

ANALYZE VERBOSE tb_teste_stat;

. . . 3000 rows in sample, 10000048 estimated total rows

3000 linhas de amostra (3000 rows in sample).

[>] Definindo default_statistics_target = 1000; 300000 amostras:

SET default_statistics_target = 1000;

[>] Execução de ANALYZE de forma verbosa:

ANALYZE VERBOSE tb_teste_stat;

. . . 300000 rows in sample, 10000000 estimated total rows

300000 linhas de amostra (300000 rows in sample).


203 10. Rotinas de manutenção

[>] Consulta de teste com EXPLAIN ANALYZE:

EXPLAIN ANALYZE SELECT count(*) FROM tb_teste_stat WHERE campo % 17 = 0;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=107800.30..107800.31 rows=1 width=8) (actual time=655.477..657.230 rows=1 loops=1)
-> Gather (cost=107800.08..107800.29 rows=2 width=8) (actual time=655.315..657.226 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=106800.08..106800.09 rows=1 width=8) (actual time=650.688..650.688 rows=
-> Parallel Seq Scan on tb_teste_stat (cost=0.00..106748.00 rows=20833 width=0) (actual time=
Filter: ((campo % 17) = 0)
Rows Removed by Filter: 3137520
Planning Time: 0.111 ms
Execution Time: 657.264 ms

[>] Para evitar influência do buffer, apagar a tabela:

DROP TABLE tb_teste_stat;

[>] Definindo default_statistics_target para 1:

SET default_statistics_target = 1;

[>] Nova criação de tabela de teste com um milhão de registros:

CREATE TABLE tb_teste_stat


AS SELECT (random() * 1000000)::int AS campo
FROM generate_series(1, 10000000);

[>] Execução de ANALYZE de forma verbosa:

ANALYZE VERBOSE tb_teste_stat;

. . . 300 rows in sample, 10000048 estimated total rows


204 10. Rotinas de manutenção

[>] Consulta de teste com EXPLAIN ANALYZE:

EXPLAIN ANALYZE SELECT count(*) FROM tb_teste_stat WHERE campo % 17 = 0;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=107800.60..107800.61 rows=1 width=8) (actual time=4500.159..4502.102 rows=1 loops=1
-> Gather (cost=107800.38..107800.59 rows=2 width=8) (actual time=4500.041..4502.095 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=106800.38..106800.39 rows=1 width=8) (actual time=4494.869..4494.870 row
-> Parallel Seq Scan on tb_teste_stat (cost=0.00..106748.30 rows=20833 width=0) (actual time=
Filter: ((campo % 17) = 0)
Rows Removed by Filter: 3136695
Planning Time: 0.095 ms
Execution Time: 4502.136 ms

Com uma quantidade menor de amostras, o planejador de consultas faz uma execução pobre
e, consequentemente, a performance é degradada.
205 10. Rotinas de manutenção

Autovacuum
O autovacuum é um recurso opcional, porém altamente recomendado para automatizar pro-
cessos de vacuum e analyze nas tabelas, assim marcando tuplas mortas como reutilizáveis e
atualizando estatísticas.
O processo de autovacuum busca por tabelas que tenham sofrido por grandes escritas DML
(INSERT, UPDATE ou DELETE).
Além do parâmetro de configuração autovacuum, o parâmetro track_counts também precisa es-
tar habilitado, devido ao autovacuum utilizar a facilidade de coleção de estatístias.ounts para
buscar por tabelas do postgresql.conf habilitadas. O daemon autovacuum é composto por
múltiplos processos. O processo daemon persistente, chamado autovacuum launcher, é re-
sponsável por criar os processos autovacuum workers para todas bases de dados.
O autovacuum launcher distribui o trabalho ao longo do tempo, tentando iniciar um autovac-
uum worker em cada banco de dados a cada autovacuum_naptime segundo. Sendo assim, se a
instância tem N bases de dados, um novo worker será executado a cada autovacuum_naptime/N
segundos.
A quantidade de workers para rodar ao mesmo tempo é definida por autovacuum_max_workers.
Caso exista mais bases de dados do que o valor definido em autovacuum_max_workers, a próxima
base a ser processada ocorrerá assim que um worker que já estiver rodando terminar.
Cada worker, no banco de dados em que estiver atuando, vai verificar cada tabela executando
VACUUM e / ou ANALYZE conforme necessário.
Como dica de ajuste de autovacuum_max_workers, considere o número de grandes tabelas + 1,
pois, mesmo que os processos workers fiquem ocupados durante muito tempo, sempre haverá
um processo sobrando para outras.
O parâmetro de configuração log_autovacuum_min_duration pode ser usado para monitorar a
atividade de autovacuum.
Pode-se ter mais de um autovacuum worker atuando na mesma base de dados, porém, cada
worker tenta não repetir o que já foi feito por outros workers.
A quantidade de workeres não é contabilizada para os limites max_connections ou
superuser_reserved_connections.
Tabelas cujo valor no campo relfrozenxid do catálogo pg_class for maior que autovacuum_freeze_max_age
transações antigas são sempre vacumizadas. Isso também se aplica para aquelas tabelas
cujo tempo máximo de congelamento tenha sido modificado via parâmetros de armazena-
mento. Se o número de tuplas obsoletas desde o último vacuum exceder o “vacuum thresh-
old”, a tabela será vacumizada.
O autovacuum faz uso de travas (locks) SHARE UPDATE EXCLUSIVE, que normalmente
só conflitam com operações de alteração de estruturas de objetos. Ainda assim, se algum
comando precisar desse tipo de lock, o autovacuum é interrompido e a trava liberada.
As operações de autovacuum (vacuum e analyze) têm limitação de memória definida pelo
valor do parâmetro de configuração autovacuum_work_mem. Se esse valor for insuficiente, será
necessário criar arquivos temporários, resultando em mais I/O, o que pode causar degradação
206 10. Rotinas de manutenção

de desempenho.

Outros parâmetros de ajustes do autovaccum:

• autovacuum_vacuum_threshold
Quantidade mínima de linhas atualizadas ou removidas para disparar um vacuum na
tabela. Padrão 50;

• autovacuum_analyze_threshold
Quantidade mínima de linhas atualizadas ou removidas para disparar um analyze na
tabela. Padrão 50;

• autovacuum_vacuum_scale_factor Fração da quantidade de tuplas da tabela para somar com


autovacuum_vacuum_threshold e, então, disparar um vacuum na tabela. Padrão 0.2;

• autovacuum_analyze_scale_factor
Fração da quantidade de tuplas da tabela para somar com autovacuum_analyze_threshold
e, então, disparar um analyze na tabela. Padrão 0.1.

Ponto de partida de vacuum (vacuum threshold)

Execuções de UPDATEs e DELETEs criam tuplas mortas e, ao excederem o limite, o autovacuum


executará VACUUM. Esse limite chama-se de ponto de partida de vacuum (vacuum threshold).
A fórmula abaixo é usada para decidir se a vacumização é necessária:

vacuum threshold = vacuum base threshold + vacuum scale factor * number of tuples

Onde:

vacuum base threshold: autovacuum_vacuum_threshold;


vacuum scale factor : autovacuum_vacuum_scale_factor;
number of tuples: pg_class.reltuples;

[>] Verificar o valor do parâmetro autovacuum_naptime:

SHOW autovacuum_naptime;

autovacuum_naptime
--------------------
1min
207 10. Rotinas de manutenção

[>] Alterar o parâmetro autovacuum_naptime para um valor baixo:

ALTER SYSTEM SET autovacuum_naptime = 3;

Assim o autovacuum vai ter um tempo de “soneca” menor.

[>] Recarregar as configurações:

SELECT pg_reload_conf();

pg_reload_conf
----------------
t

[>] Verificando novamente o parâmetro:

SHOW autovacuum_naptime;

autovacuum_naptime
--------------------
3s

Com 3 (três) segundos fica mais rápido percebermos as mudanças.

[>] Criação da tabela de teste:

CREATE TABLE tb_teste_threshold AS


SELECT generate_series(1, 1000000) AS campo;

[>] Obtendo o ponto de partida de vacuum da tabela:

SELECT
(s1.setting::REAL + s2.setting::REAL * c.reltuples::REAL)
AS "vacuum threshold"
FROM pg_settings AS s1, pg_settings AS s2, pg_class AS c
WHERE s1.name = 'autovacuum_vacuum_threshold'
AND s2.name = 'autovacuum_vacuum_scale_factor'
AND c.relname = 'tb_teste_threshold';
208 10. Rotinas de manutenção

vacuum threshold
------------------
200050
209 10. Rotinas de manutenção

[>] Quantas vezes o autovacuum atuou na tabela?

SELECT
autovacuum_count
FROM pg_stat_user_tables
WHERE relname = 'tb_teste_threshold';

autovacuum_count
------------------
1

[>] Apagando uma linha a mais que vacuum threshold:

DELETE FROM tb_teste_threshold WHERE campo <= 200051;

Agora aguarde três segundos. . .

[>] Número de tuplas mortas e quantas vezes o autovacuum atuou na tabela:

SELECT
n_dead_tup,
autovacuum_count
FROM pg_stat_user_tables
WHERE relname = 'tb_teste_threshold';

n_dead_tup | autovacuum_count
------------+------------------
0 | 2

Nenhuma tupla morta e, mais uma vez, o autovacuum atuou na tabela por conta das tuplas
mortas geradas ser um número maior que vacuum threshold.

[>] Deixando o parâmetro autovacuum_naptime com seu valor padrão:

ALTER SYSTEM RESET autovacuum_naptime;

[>] Recarregar as configurações:

SELECT pg_reload_conf();
210 10. Rotinas de manutenção

[>] Verificar novamente o parâmetro:

SHOW autovacuum_naptime;

autovacuum_naptime
--------------------
1min

Nota-se o autovacuum em ação e conceitos inerentes como vacuum threshold.


Assim que foram apagadas vacuum threshold + 1 linhas e o tempo de adormecimento
(autovacuum_naptime) do autovacuum terminou, a tabela recebeu a atuação do autovacuum,
tendo eliminadas todas suas tuplas mortas.
211 10. Rotinas de manutenção

Revisao
1. P: O que são tuplas mortas?
R:
2. P: Além da “limpeza” de tuplas mortas, qual a importância do vacuum?
R:
3. P: Qual a importância do autovacuum?
R:
4. P: É necessário realizar vacuum full periodicamente?
R:
5. P: O que acontece se o PostgreSQL atingir o número limite de transações sem que
ocorra o freeze?
R:
11
Performance
• Performance / Tuning do PostgreSQL
• Hardware
• Sistema Operacional
• Configurações do PostgreSQL (postgresql.conf)
• Aplicação
• Otimização de consultas
• Pool de conexões
• Bases de dados OLTP (OnLine Transaction Processing)
• Bases de dados OLAP (OnLine Analytical Processing)
• pgbench e benchmarks
• Visões materializadas
• Tabelas não logadas - Unlogged tables
• Fillfactor - Fator de preenchimento
• Plano de execução
• Huge pages

212
213 11. Performance

Performance / Tuning do PostgreSQL


O desempenho de um sistema gerenciador de banco de dados é extremamente relevante para
um negócio.
A agilidade da aplicação (OLTP, web, OLAP) utilizada pelo cliente depende muito do SGBD,
que muitas vezes é o responsável por uma lentidão em todo o sistema.
Por isso, como diz o ditado, “tempo é dinheiro”. . . a performance do servidor de banco de
dados é crucial.
As configurações padrão do PostgreSQL são muito modestas, pois visam compatibilidade em
vez de desempenho.
No entanto, as configurações do PostgreSQL (em especial o arquivo postgresql.conf) não são
as únicas coisas responsáveis pelo desempenho do banco.
Para obter um desempenho otimizado no PostgreSQL, é preciso atuar em cada uma das
camadas que envolvem o sistema.
Camadas essas que são, por ordem da mais baixa à mais alta: hardware, sistema operacional,
configurações do PostgreSQL, aplicação e otimização de consultas.
Outro fator a ser considerado de grande importância para dimensionamento de recursos é o
tipo de aplicação: Web, OLTP ou DW (OLAP).
214 11. Performance

Hardware
É a parte física e também a camada mais baixa de todo o sistema que envolve a aplicação.
A escolha do hardware deve ser criteriosa e avaliar não somente o desempenho proporcionado,
mas também outros quesitos como relação de custo-benefício, durabilidade (MTBF – Mean
Time Between Failures: Tempo Médio Entre Falhas), compatibilidades (entre outros hardwares
e sistema operacional), garantia do fornecedor, suporte etc. Os itens físicos mais importantes,
por ordem decrescente, são: armazenamento -> memória -> processador -> rede.
215 11. Performance

Disco / armazenamento

O tipo de dispositivo responsável pelo armazenamento permanente dos dados é também o


maior desafio para um sistema gerenciador de banco de dados, pois é a parte mais lenta do
sistema. A grande maioria dos dispositivos são mecânicos e dispositivos mecânicos natural-
mente vão ser mais lentos.
Os totalmente eletrônicos (NVMEs e SSDs) são mais rápidos, mas têm uma durabilidade
muito inferior a um disco eletro-mecânico.
É de extrema importância para um banco de dados que tenha discos dedicados.
Não se deve ter outros serviços concorrendo com I/O de disco.

A ordem de desempenho por tipo de armazenamento é:

NVME -> SSD -> SAS -> SCSI -> SATA

Write-back e write-through

Há um recurso em controladoras de disco que é chamado de write-back. Ele proporciona


um maior desempenho devido ao fato dos dados a serem gravados ficarem em cache e serem
escritos posteriormente.
Quanto mais cache uma controladora possuir, melhor.
O grande problema desse recurso é se houver uma queda de energia e os dados não tiverem
sido gravados.
Em oposição ao write-back, temos o write-through, que grava os dados assim que possível
em detrimento da performance.
Para poder usufruir do desempenho proporcionado pelo write-back com segurança, é preciso
que a controladora tenha uma BBU (Battery Backup Unity ), ou também conhecida como
BBM (Battery Backup Module).
A BBU tem uma bateria que mantém a energia do cache da controladora em caso do sistema
sofrer com falta de energia elétrica. Após o reestabelecimento da energia, os dados em cache
serão escritos para o(s) disco(s).
É importante lembrar também que, para poder se beneficiar do desempenho oferecido pelo
write-back com uma BBU, é necessário que o sistema de arquivos esteja com a opção write
barriers desabilitada.
Se write-barriers estiver desabilitado, tenha certeza que a bateria está OK, pois sua falha pode
causar perda de dados.

RAID – Arranjos de discos

Sua ideia consiste em agrupar discos físicos e formar uma única unidade lógica.
Cada disco que faz parte do conjunto é chamado de spindle.
É preferível mais spindles de menor capacidade para compor o RAID do que menos discos de
216 11. Performance

menor capacidade, pois grandes discos tendem a ser mais lentos. Ou seja, na prática, é melhor
20 discos de 1 TB do que 10 de 2 TB, por exemplo.

RAID 0 (striping)

Mínimo de 2 (dois) discos, transforma N discos em um disco lógico.


A utilização de RAID 0 é extremamente restrita em bancos de dados, devido à sua falta de
segurança.
A única aplicação plausível de RAID 0 em bancos de dados é para dados não críticos, como
dados temporários.

Vantagens:

• Velocidade;
• Custo baixo de expansão;
• Aproveitamento máximo de capacidade.

Desvantagem:

• Insegurança; basta que um dos discos se corrompa para que se perca tudo.

RAID 1 (mirroring)

Mínimo de 2 (dois) discos, que são espelhados entre si.


Por sua confiabilidad, é o mínimo recomendado para dados críticos.
É recomendável que os discos que compõem um espelhamento sejam de lotes diferentes para
evitar que falhem ao mesmo tempo.

Vantagens:

• Aumento de velocidade para leitura;


• Segurança (alta disponibilidade).

Desvantagens:

• Aproveitamento lógico de dados reduzidos pela metade;


• Custo.
217 11. Performance

RAID 5

Mínimo de 3 (três) discos, utiliza um sistema ECC (Error Correcting Code – Detecção e
Correção de Erros) que distribui informações de paridade ao longo dos discos.
Por sua distribuição de informações de paridade, a leitura de dados sequenciais é prejudicada.
Seu nível de segurança é discutível; não permite que mais de 1 (um) disco falhe.
Levando em conta que se for feita uma aquisição de discos de mesmo lote, considerando o
MTBF de cada um, a probabilidade de mais de um dar problema ao mesmo tempo existe, o
que poderia ser catastrófico neste tipo de arranjo. Ainda assim é bem mais seguro do que o
RAID 0.

Vantagens:

• Aumento de velocidade para leitura (dados randômicos);


• Ótimo aproveitamento de dados;
• Custo.

Desvantagens:

• Segurança;
• Velocidade de escrita reduzida devido à complexidade do sistema de ECC;
• Desempenho de leitura para dados sequenciais.

RAID 6

Sistema similar ao RAID 5, tem um mínimo de 4 (quatro) discos, seu sistema de ECC é o
dobro de dados de paridade comparado ao RAID 5.
O RAID 6 é relativamente novo, não suportado por todas controladoras e pouco mais seguro
do que o RAID 5, pois permite que até 2 (dois) discos falhem.

Vantagens:

• Aumento de velocidade para leitura (dados randômicos);


• Ótimo aproveitamento de dados;
• Custo.

Desvantagens:

• Segurança;
• Escrita reduzida devido à complexidade do sistema de ECC;
• Desempenho de leitura para dados sequenciais reduzido.
218 11. Performance

RAID 10

É o tipo de arranjo ideal, une a segurança de espelhamento do RAID 1 com a velocidade do


RAID 0.
O mínimo de discos necessário é 4.

Vantagens:

• Segurança;
• Velocidade.

Desvantagens:

• Custo;
• Baixo aproveitamento de dados.
219 11. Performance

DAS – Direct Attached Storage

Sistema de armazenamento externo conectado diretamente a um ou mais servidores sem uso


de rede por meio de HBA (Host Bus Adapter ).

• Quase sempre é mais rápido;

• Quase sempre tem uma relação custo-benefício maior;

• Escalável;

• Gerenciável.
220 11. Performance

NAS – Network Attached Storage

Sistema de armazenamento externo que consiste em um servidor conectado à rede unicamente


para fornecer serviços de armazenamento de dados para outros dispositivos da rede.

• Custo alto;

• Não muito confiável para bases de dados, pois geralmente usam algo como NFS;

• Altamente configurável;

• Altamente gerenciável;

• Compartilha seus recursos.


221 11. Performance

SAN – Storage Area Network

Sistema de armazenamento externo que segue o conceito de armazenamento de dados em


rede, pois é uma rede cuja única finalidade é armazenar dados.

• Custo alto;

• Geralmente usa iSCSI;

• Limitado pela banda de rede que é quase sempre mais lenta (exceto de 10 Gb) do que
o DAS;

• Altamente configurável;

• Altamente gerenciável;

• Recurso compartilhado.
222 11. Performance

Memória RAM

Dispositivo de armazenamento de dados volátil, cuja velocidade é muito maior do que um


dispositivo de armazenamento persistente (HD, SSD, NVME e etc).
É de vital importância no desempenho.
Para um melhor aproveitamento no PostgreSQL, o ajuste de alguns parâmetros são necessários,
em especial shared_buffers.
Para evitar corrupções de dados, é fortemente recomendado o uso de memórias com detecção
e correção de erro (ECC: Error Correction Code) e com registradores (registered).
Enquanto houver mais memória do que for necessário, por menor que seja, o servidor será
mais rápido, evitando assim fazer I/O por swap ou arquivos temporários.
Aplicações web e OLTP recomenda-se, se possível, que toda a base (ou pelo menos boa parte
dela) caiba na RAM. O ideal é que se consiga armazenar as ordenações e operações com
tabelas hashes, como agregações e junções.
Para OLAP (relatórios e estatísticas), deve ser suficiente operações de ordenação, joins (junções),
funções de agregação e qualquer outra operação no servidor pertinente a DWs que requiram
muita memória.

O parâmetro de configuração work_mem

É também muito importante para performance por usar memória em vez de disco.
Determina a quantidade de memória usada para operações de consulta, tais como ordenação
e tabelas de dispersão (hash tables). ORDER BY, DISTINCT, agregações baseadas em hash e
subconsultas IN são casos que pode-se obter mais desempenho aumentando seu valor.
O diretório $PGDATA/base/pgsql_tmp é o destino de arquivos temporários criados caso seu valor
seja insuficiente para uma operação. Esses arquivos em sua nomenclatura têm o PID do
processo que está rodando a query que os gerou.
Como dica de ajuste, é possível utilizar a seguinte fórmula:

Memória RAM * 0.25 / max_connections

Quanto menor for o parâmetro max_connections, menos recursos alocados para conexões e,
assim, pode-se aumentar o valor de work_mem.

Aviso

work_memdeve ser ajustado com cautela, pois para uma consulta complexa que tenha várias
operações de classificação ou hash podem ser executadas paralelamente. Cada uma dessas
operações está permitida a usar o valor especificado, o que pode levar a um esgotamento de
memória.
223 11. Performance

CPU

O escalonamento com múltiplos processadores não é linear.


Poucos processadores rápidos são mais eficientes do que muitos processadores lentos.
Apesar de ser quase o último item na escala de escolha de hardware, o banco de dados se
beneficiará de sistemas multiprocessados, com processadores de 64 bits e velocidades de clock
elevadas, pois isso ajudará muito no processamento das conexões e consultas.
Devido às características do PostgreSQL, a comunicação entre os processos é muito importante
para a performance. Quanto mais núcleos no mesmo “chip” mais rápida será essa comunicação.
Portanto, processadores modernos com pelo menos 4 núcleos, são indicados.
224 11. Performance

Rede

A rede normalmente não representa um grande problema para bancos de dados, mas even-
tualmente pode sim ser um gargalo.
Problemas podem ser ocasionados por switches, cabeamento, máquinas sobrecarregadas ou
mesmo uma rede de baixa velocidade.
Há ainda o problema de perda de pacotes causado quando a máquina de destino não respondeu
em tempo hábil, por congestionamento, colisão, porta fechada ou indisponibilidade.
Entre o servidor de banco de dados e o servidor de aplicação ou em caso de replicação um
servidor secundário (se apenas 2 (dois)), pode até usar um cabo crossover.
Levar em consideração também backups remotos, ou seja, o quanto de acordo com o volume
de dados.
No PostgreSQL, problemas de rede o afetam com connection timeout (tempo esgotado de
conexão), represamento de logs de transação causado ou por um standby que ainda não con-
sumiu esses arquivos ou devido ao arquivamento ter falhado.
AS views de sistema pg_stat_replication e pg_stat_archiver, respectivamente, monitoram repli-
cação e arquivamento.
225 11. Performance

Sistema Operacional
O sistema operacional e suas particularidades influenciam também no desempenho do banco.
Sistemas operacionais da família Unix (Linux, BSDs, AIX, Solaris, etc) comprovadamente
entregam maior desempenho para o PostgreSQL do que o Windows.
Os temas tratados nesta sessão serão todos relativos ao Linux.

Kernel

O kernel influencia na performance de um servidor.


Há parâmetros que podemos modificar via utilitário sysctl. Para persistir modificações de
parâmetros do kernel, utiliza-se o arquivo /etc/sysctl.conf.

Comunicação inter-processos

Em inglês Inter process communication (IPC), é um mecanismo que permite aos processos se
comunicarem e sincronizarem suas ações.
A comunicação entre esses processos podem ser encarada como uma forma de cooperação
entre eles. Processos podem comunicar entre si por meio de memória compartilhada, troca de
mensagens e semáforos.
O PostgreSQL utiliza memória compartilhada e semáforos para fazer a comunicação inter-
processos.
O utilitário ipcs pode ser utilizado para verificar informações IPC.

Memória compartilhada

É uma parte da RAM separada para que um determinado grupo de processos possa acessá-la,
evitando dados redundantes.
Mesmo não utilizando o SysVIPC para alocação dos shared buffers, o PostgreSQL continua
realizando uma pequena alocação de memória compartilhada por SysVIPC para seu funciona-
mento.
Atualmente, é possivel utilizar o parâmetro shared_memory_type, configurado para que o Post-
greSQL utilize o SysVIPC para alocação dos shared buffers.
226 11. Performance

[#] Verificar segmentos de memória compartilhados alocados:

ipcs -m

------ Shared Memory Segments --------


key shmid owner perms bytes nattch status
0x00060a8a 0 postgres 600 56 8
0x0006120b 1 postgres 600 56 6

Uma linha por segmento de memória compartilhada do usuário postgres. Uma tem 8 processos
e a outra, 6.

Semáforos

São recursos providos pelo kernel para tratar condições de corrida, os quais, assim como
a memória compartilhada, podem ser obtidos através do “SysVIPC” ou através do padrão
POSIX.
Até a versão 9.6, apenas o “SysVIPC” era utilizado, a partir da versão 10, em ambientes
Linux e FreeBSD o POSIX passou a ser o mecanismo utilizado. Essa mudança ocorreu para
simplificar a configuração dos limites de semáforos do “SysVIPC”.
Caso esteja utilizando algum S.O. onde o SysVIPC é utilizado, segue a documentação abaixo
para realizar os cálculos de limite necessários:

https://www.postgresql.org/docs/current/kernel-resources.html

Swapiness

É a maneira que o kernel do Linux fornece para ajustar a configuração que controla a frequência
que a swap é usada, cuja faixa de valores varia de 0 (zero) a 100 (cem).
Cujo valor corresponde à porcentagem de RAM restante, que é o ponto de partida para se
começar a fazer swap.
Vale lembrar que o I/O, como é feito o processo de swap, é muito mais lento do que o uso de
RAM, portanto, impacta diretamente na performance do sistema.
Uma configuração vm.swappiness = 1, por exemplo, significa que será evitado fazer swap a não
ser que seja absolutamente necessário, ou seja, tem 99% de RAM usada.

[$] Verificando o valor atual de swappiness:

sysctl vm.swappiness

vm.swappiness = 60
227 11. Performance

[#] Cria um arquivo de configuração de kernel para o PostgreSQL e ajusta swappiness:

echo 'vm.swappiness = 1' >> /etc/sysctl.d/pgsql.conf

[#] Aplica as configurações do arquivo:

sysctl -p /etc/sysctl.d/pgsql.conf

[$] Verificando novamente o valor atual de swappiness:

sysctl vm.swappiness

vm.swappiness = 1

Memory overcommit

Em um programa user space, ao reservar memória (função em C malloc()), se tiver retorno


NULL significa que não há memória disponível.
Por padrão, o Linux aceita a maior parte das requisições por mais memória, pressupondo que
muitos programas requiram mais memória do que precisam.
Essa preposição estando certa permite que mais programas rodem na mesma memória ou pode
fazer rodar um programa que exija mais memória do que está disponível.
Se mesmo através desse recurso de “achar” mais memória não for possível alocar memória
para um programa, pode haver efeitos indesejados.
O OOM Killer (Out Of Memory Killer ) é invocado e, então, através de um algoritmo heurístico,
seleciona algum processo para matar. Há uma grande polêmica em torno da escolha da
“vítima”.
Pode até não ser um processo root, algum processo que esteja fazendo I/O de disco, algum
que já esteja há muito tempo fazendo algum cálculo.
Resumindo, Memory Overcommit é um recurso que permite que, ao se esgotar a memória
(RAM + swap), um programa que requisitar alocação de memória após isso provavelmente
será atendido utilizando a mesma memória que outro(s) programa(s). Mas caso isso não
for possível o OOM Killer matará algum processo aleatoriamente, seguindo um algoritmo
heurístico.
228 11. Performance

Parâmetros de memory overcommit do kernel

• overcommit_memory (vm.overcommit_memory)
Define o recurso memory overcommit, cujos possíveis valores são:

– 0 (padrão) - Gerenciamento heurístico de overcommit


Faz com que o kernel tente estimar a quantidade de memória livre restante quando
programas user space pedem mais memória.
O usuário root é permitido alocar um pouco mais de memória neste modo.

– 1 - Sempre fazer overcommit


Apropriado para algumas aplicações científicas, faz com que o kernel “minta”
afirmando que sempre há memória suficiente até que ela realmente se esgote.

– 2 - Não fazer overcommit; “never overcommit”


Tenta prevenir qualquer overcommit de memória.
Dependendo da quantidade de memória que for usada, na maioria das situações
isso significa que um processo não será terminado enquanto acessar páginas, mas
receberá erros de alocação de memória como apropriado.
Útil para aplicações que deseja-se garantir que suas alocações de memória estarão
disponíveis no futuro sem ter que iniciar cada página.
O limite atual de overcommit e quantidade comprometida (commited) estão disponíveis
em /proc/meminfo como CommitLimit e Committed_AS respectivamente.

[$] Verificando o valor atual de vm.overcommit_memory:

sysctl vm.overcommit_memory

vm.overcommit_memory = 0

[#] Configurar para nunca fazer overcommit:

echo 'vm.overcommit_memory = 2' >> /etc/sysctl.d/pgsql.conf

[#] Aplica as configurações do arquivo:

sysctl -p /etc/sysctl.d/pgsql.conf
229 11. Performance

[$] Verificando novamente o valor atual de vm.overcommit_memory:

sysctl vm.overcommit_memory

vm.overcommit_memory = 2

• overcommit_ratio (vm.overcommit_ratio)
Ao se configurar overcommit_memory para 2, o espaço comprometido (commited) não está
permitido a exceder swap mais o percentual da memória RAM referida no valor deste
parâmetro, conforme segue a fórmula abaixo:

swap + (overcommit_ratio * 0.01 * RAM)

Seu valor padrão é 50, que significa 50%.

A Relação entre memory overcommit e PostgreSQL

O comportamento padrão da memória virtual do Linux não é otimizado para o PostgreSQL.


Devido à maneira que o kernel implementa memory overcommit, o kernel pode matar o pro-
cesso principal do PostgreSQL, ou qualquer processo backend, caso algum processo tente
utilizar mais memória que o disponível no sistema.
Se isso acontecer, será vista uma mensagem do kernel como a que segue:

Out of Memory: Killed process 12345 (postgres).

Isso indica que o processo postgres foi terminado devido à pressão de memória.
Embora as conexões à base de dados continuem a funcionar normalmente, nenhuma conexão
nova será aceita. Para se recuperar, o PostgreSQL precisa ser reiniciado.

Limpeza do page cache

A seguir, descrição de parâmetros do kernel a respeito de limpeza de cache:

• vm.dirty_background_ratio
É o percentual de memória preenchido com páginas sujas que precisam ser escritas em
segundo plano (background) no armazenamento.
Seu valor é inteiro e seu intervalo é de 0 a 100, seu valor padrão costuma ser 10 na
maior parte das distribuições Linux.
Para operações de escritas intensas pode-se obter maior desempenho diminuindo seu
230 11. Performance

valor.

• vm.dirty_background_bytes
Sua ideia é parecida com a de vm.dirty_background_ratio, porém seu valor é absoluto em
bytes.
Apenas um dos dois deve ser especificado.

• vm.dirty_ratio
Tem a mesma lógica que vm.dirty_background_ratio, mas para escritas em primeiro plano,
fazendo com que a aplicação seja bloqueada.
Seu valor deve ser maior que vm.dirty_background_ratio, o que garante que processos em
segundo plano serão lançados antes dos de primeiro para evitar bloqueio da aplicação
tanto quanto possível.
Deve-se ajustar a diferença entre os dois ratios dependendo da carga de I/O.

• vm.dirty_bytes
Mesma lógica de vm.dirty_ratio, porém com valores absolutos em bytes.

Quando há muita memória RAM (e. g.: 1TB), pequenas frações já representarão uma quanti-
dade enorme de dados. Ao atingir vm.dirty_ratio ou vm.dirty_bytes é preciso aguardar a escrita
volumosa de dados, o que impactará o sistema. Nesse caso, é melhor esses valores reduzidos.

Readahead

Recurso do kernel Linux para melhorar performance de leitura de arquivos, em que é feita uma
busca antecipada de arquivos.
Se o kernel tiver alguma razão para acreditar que um determinado arquivo será lido na se-
quência, ele vai tentar colocá-lo em memória antes de a aplicação requisitá-lo.
É uma chamada de sistema que carrega o conteúdo de um arquivo em páginas de cache.
Ao acessar um arquivo posteriormente, seu conteúdo é lido da memória em vez do armazena-
mento, que é muito mais rápido.
Muitas distribuições Linux usam o recurso readahead em uma lista de arquivos mais comu-
mente usados para acelerar o processo de boot.
O valor padrão é 256 para drives comuns e as unidades são normalmente em 512 bytes, assim
sendo o valor padrão igual a 128kb (256 * 512 bytes).
231 11. Performance

[#] Com o utilitário blockdev fazer o relatório e filtrar apenas pelos dispositivos de armazena-
mento disponíveis (exemplo):

blockdev --report | head -1 && \


blockdev --report | egrep '(nvme0n1|sda|sdb)$'

RO RA SSZ BSZ StartSec Size Device


rw 256 512 4096 0 512110190592 /dev/nvme0n1
rw 256 512 4096 0 512110190592 /dev/sda
rw 256 512 4096 0 480103981056 /dev/sdb

[#] Verificando um dispositivo de armazenamento individualmente:

blockdev --getra /dev/sda

256

O valor padrão, 256: 128kb.

[#] Ajustando um dispositivo de armazenamento para 2MB de readahead:

blockdev --setra 4096 /dev/sda

Sendo 4096 * 512 = 2097152 (em bytes) ou simplesmente 2MB.

Observação

O comando blockdev --setra não é persistente, ou seja, ao reiniciar o servidor, tudo voltará ao
padrão.
Caso queira que em caso de reboot o servidor já suba com esse comando, pode inseri-lo no
arquivo /etc/rc.local.
232 11. Performance

[#] Criação do arquivo /etc/rc.local:

cat << EOF > /etc/rc.local


#!/bin/bash

blockdev --setra 4096 /dev/sda

exit 0
EOF

[#] Dar permissão de execução ao arquivo:

chmod +x /etc/rc.local

[#] Iniciar o serviço:

systemctl start --now rc.local


233 11. Performance

Sistema de arquivos

O sistema de arquivos utilizado por um banco de dados influencia diretamente em sua segu-
rança e confiabilidade.
A confiabilidade de um sistema de arquivos está no recurso de journaling, que por outro lado
tem um custo de I/O.
Isso pode ser contornado utilizando um disco com partições específicas para guardar os logs
(journals) dos outros, mas é necessário fazer testes para verificar cada caso se o desempenho
ganho vale a pena financeiramente.
A respeito de qual utilizar, primeiro devemos analisar o que vai ser armazenado e posterior-
mente fazer testes de carga anotando os resultados pra decidir pelo mais rápido. A performance
de um tipo de filesystem pode variar entre versões do kernel do Linux.

De uma forma geral podemos colocar da seguinte maneira as partições (para Linux):

• Índices -> XFS, ext4 (noatime, nodiratime, nobarrier*);


• Dados -> XFS, ext4 (noatime, nodiratime, nobarrier*);
• WAL -> XFS, ext4 (noatime, nodiratime, nobarrier*);
• Logs -> XFS, ext4 (noatime, nodiratime, nobarrier*);
• pg_stat_tmp -> tmpfs (uid, gid, size).

Observação

Exceto o último, que é um armazenamento virtual em memória RAM, para os outros é acon-
selhável que tenham uma unidade de armazenameno dedicada para evitar concorrência de
I/O.

* Somente para controladoras que têm uma BBU e a opção write-back, caso contrário não
utilizar nobarrier.
234 11. Performance

Journaling

É o recurso de sistemas de arquivos que evita perdas de dados e permite recuperação numa
situação de crash.
Ao gravar um arquivo em armazenamento, há três fases:

1) Registro no bloco de metadados dos blocos utilizados;


2) Gravação no bloco;
3) Registro de metadados do bloco gravado.

O recurso de journaling permite a recuperação de dados mais rápida e confiável cujo funciona-
mento é gravar metadados ou os próprios dados em uma divisão denominada “journal” antes
de escrever nos blocos.
Após a escrita bem sucedida nos blocos, o conteúdo do journal pode ser removido.
Em caso de crash durante escrita, com journaling, a recuperação é feita a partir dos dados
contidos no journal. Esse mecanismo é mais eficiente pelo fato de ter que buscar no journal
em vez de todos os blocos do sistema de arquivos.
Com journaling ganha-se em confiabilidade e agilidade (para uma possível necessidade de re-
cuperação de crash), porém, há um pouco de perda de desempenho causado pelo overhead.
Isso conforme o sistema de arquivos, já que a escrita de dados pode ser feita praticamente
duas vezes antes de ser considerada finalizada. Assim, é importante saber como é feito o
journaling do sistema de arquivos escolhido.

Write barriers

Recurso, bem menos popular que journaling, é de grande importância para uso com SGBDs,
pois é ele que garante que os dados sejam fisicamente gravados após um fsync, garantindo
que o conteúdo do cache da controladora seja despejado no fisicamente no dispositivo de
armazenamento.
Sem write barriers, após um fsync os dados são despejados do cache do sistema operacional
mas podem permanecer no cache da controladora e se um crash ocorrer nesse momento, os
mesmos serão perdidos pois o cache da controladora é baseado em memória volátil.
O uso de write barriers aumenta a confiabilidade porém praticamente inutiliza todas as van-
tagens de desempenho de se possuir cache na controladora, pois toda gravação com fsync vai
precisar esperar pela escrita no dispositivo físico de armazenamento.
É possível desligar writer barriers com segurança e aproveitar o desempenho da escrita no
cache quando se utiliza controladoras/storage que possuem bateria. Assim, em caso crash, a
bateria mantém o conteúdo do cache da controladora durante horas ou dias dependendo do
equipamento.
Em caso de se utilizar controladora sem bateria é preciso manter o recurso de write barriers
habilitado. Em sistemas de arquivos sem esse recurso deve-se desabilitar o cache.
No caso de se utilizar controladora/storage com bateria, recomenda-se desligar write barriers.
235 11. Performance

UUID no fstab

Não é obrigatório, mas o uso de UUID ao invés do endereço do dispositivo é uma boa prática,
pois se os discos forem trocados de lugar não será levada em conta a ordem que estão, mas
sim sua ID no sistema.

Coisas a serem evitadas em servidores de banco de dados

• Serviços concorrentes
Ter o mínimo possível de serviços rodando no servidor, pois os mesmos também con-
somem recursos que vão interferir no desempenho do banco de dados.
Não faz sentido ter um servidor de banco de dados rodando junto com um servidor web,
um servidor de e-mails ou um servidor de arquivos.

• Interface gráfica
Toda a administração pode ser feita tranquilamente via comandos e edição de arquivos
que podem ser executados pessoalmente ou remotamente via SSH.
Uma interface gráfica é útil para desktops, pois o usuário precisa o tempo todo ver o
que está fazendo e utilizar o mouse com frequência.
No entanto, isso tem custos (memória, processamento, etc. . . ), o que para um servidor
de banco de dados é precioso.
Por isso, um servidor Windows tende a ser mais lento do que um servidor da família de
sistemas operacionais Unix.
236 11. Performance

Aplicação
Modelagem

Deve ter critérios consistentes ao escolher o tipo de dado de uma coluna.


Verifique na documentação os limites para o tipo de dado escolhido.
Para inteiros, o tipo INTEGER (de 4 bytes) pode ser suficiente ou muito mais que suficiente,
podendo até utilizar um tipo mais leve pra isso, como o SMALLINT (de 2 bytes). Só utilize o
BIGINT (8 bytes) se realmente precisar.

Indexação

Crie índices para campos que são utilizados em condições de consultas (cláusula WHERE). Pelo
menos as consultas mais frequentes.
Crie índices para campos de chaves estrangeiras.
Fillfactor (fator de preenchimento) para um índice é a porcentagem que determina como o
método de indexação encherá as páginas de índices.
O quão cheias essas páginas ficarão em porcentagem.
Tabelas estáticas pode-se deixar em 100 (representando 100%).
Tabelas que sofrem muitas alterações um valor de 80 ou menos pode ser mais adequado, mas
quanto menor for o fator de preenchimento, mais espaço ocupará.

ORMs

ORM: Object-Relational Mapping, que em português significa Mapeamento Objeto-Relacional.


Um ORM é uma biblioteca de uma linguagem de programação que abstrai os dados de um
banco de dados.
Internamente é feita uma tradução entre os tipos da linguagem e os tipos do banco de dados
e, por fim, é montado o comando SQL que será enviado para o banco.
A grande vantagem é que para um desenvolvedor que não tem uma sólida base de conheci-
mentos sobre SQL e teoria de banco de dados relacionais, operações CRUD (CREATE : criar,
RETRIEVE : buscar, UPDATE : atualizar, DELETE : apagar) são extremamente facilitadas.
Isso se deve à abstração que a biblioteca faz sobre a linguagem de programação utilizada.
No entanto, essa facilidade dada ao desenvolvedor, por ser algo automatizado, não pensado e
planejado cuidadosamente por um ser humano, leva a comandos não otimizados ao banco de
dados. Assim, o tempo todo o sistema fatalmente ficará lento com o grande volume de dados
e principalmente por causa do fator de escalabilidade ser muito baixo.
Para comandos otimizados na base de dados, por mais que seja trabalhoso, a melhor coisa a
ser feita é criar de forma manual e com testes previamente feitos para se escolher a melhor
forma para o planejador de consultas.
Utilize o comando EXPLAIN ANALYZE com uma massa de dados considerável para testes.
237 11. Performance

Otimização de Consultas
Testar consultas para otimizar

Para fazer a mesma coisa podem ter jeitos SQL diferentes para isso. Saber qual é o mais
eficiente só testando. Como?

EXPLAIN ANALYZE consulta;

Conforme o resultado obtido em cada consulta é possível otimizar o que deseja ser feito.
Para bases já prontas, cujas consultas já existem, também tem como melhorar.
O pgBadger [1], que através dos logs de atividades gera um relatório em formato HTML,
mostra as consultas mais lentas do sistema.

[1] https://github.com/darold/pgbadger

Dicas SQL

• Cuidado e atenção no projeto da base de dados


É claro que todo mundo sabe disso, mas vale a pena salientar a importância.
Comparado a uma obra, é o alicerce de tudo que vai ser feito a seguir.
Atentar a uma normalização correta, inserindo informações em várias tabelas de acordo
com cada entidade identificada e ter padronização de nomes de objetos.
A documentação deve ser feita desde o início.

• Evitar curingas no início e no fim


O caractere “%” em uma consulta com LIKE substitui um ou mais caracteres em uma
determinada posição.
Ao ser colocado ao mesmo tempo no começo e no fim da string, em uma tabela muito
grande, vai fazer com que sua base fique extremamente lenta.

• UNION em vez de OR
UNION pode combinar os conjuntos de resultados de duas ou mais consultas.
Utilizar UNION no lugar de OR na cláusula WHERE é mais rápido.

• Cache de consultas
A memória RAM é milhares de vezes mais rápida do que discos.
Utilizar aplicativos que fazem cache de consultas vai fazer com que seu sistema tenha
uma maior performance.
Um bom projeto desse tipo para PostgreSQL é o pgmencache
238 11. Performance

http://pgfoundry.org/projects/pgmemcache/.

• Evitar o SELECT * FROM . . .


Selecionar somente o que for necessário.
Não incluir campos na consulta sem necessidade.

• Evite subconsultas
Em muitos casos em vez de subqueries pode-se utilizar junções (joins) ou CTEs (Com-
mon Table Expression).
• Evite exibição de linhas em excesso
Uma aplicação que exibe mais que 100 tuplas de uma vez precisa ser revista.
Recomenda-se usar a técnica keyset pagination.

• Evite consultas em loops


Muitos programadores têm o vício de usar comandos (leitura / gravação) dentro de
laços em sua linguagem de programação.
Isso não é uma boa ideia, pois é como ficar martelando a base de dados.
Em vez disso, procure utilizar recursos SQL como a cláusula CASE, por exemplo.
239 11. Performance

Pool de conexões
Pool de conexões (aglomeração de conexões) faz multiplexação de conexões, ou seja, faz com
que mais de uma conexão à aplicação utilize apenas uma conexão no banco de dados.
Essa estratégia faz com que o uso de recursos seja realizado de uma maneira mais eficiente,
reduzindo o tempo de transações e consequente aumento de performance como um todo.
A implementação de pool de conexões pode ser feita pela própria aplicação ou como um
componente externo conhecido como proxy. Seja em outra máquina ou na própria onde o
PostgreSQL está instalado, é vantagem utilizar um proxy, pois as conexões nele estabelecidas
custam menos que no PostgreSQL.
Redirecionamento de conexões para diferentes servidores é uma característica interessante que
um proxy externo pode ter, com chaveamento de servidores em um cluster de alta disponibil-
idade e balanceamento de carga entre eles.
Até o momento, não há uma implementação nativa.

Fig. 11.1: Pool de conexões

PgPool II

É um middleware que funciona entre o PostgreSQL e uma aplicação cliente.


Dentre as suas funcionalidades estão connection pooling, replicação, balanceamento de carga
e outras coisas.
240 11. Performance

PgBouncer

Pode ficar em uma máquina diferente do servidor PostgreSQL.


O número de conexões clientes que se conectam a ele é muito maior que as estabelecidas no
PostgreSQL e é transparente para a aplicação.

Modos de operação:

• session - aglomeração por sessão


É o método mais “gentil”.
Quando um cliente se conecta, uma conexão é assimilada para ele enquanto estiver
conectado.
Quando o cliente se desconecta, a conexão ao servidor será devolvida ao pool.
Esse modo suporta todos recursos do PostgreSQL.

• transaction - aglomeração por transação


Uma conexão do servidor é assimilada a um cliente apenas durante uma transação.
A conexão é devolvida ao pool assim que o PgBouncer notar que a transação finalizou.
Esse modo não funciona para recursos baseados em sessão (veja tabela a seguir).

• statement - aglomeração por comando


É o método mais agressivo.
É assimilada uma conexão para o cliente somente durante um comando.
Transações com múltiplos comandos não são permitidas.

Recurso session transaction


Parâmetros de inicialização Sim Sim
SET / RESET Sim Nunca
LISTEN / NOTIFY Sim Nunca
WITHOUT HOLD CURSOR Sim Sim
WITH HOLD CURSOR Sim Nunca
Planos preparados em nível de protocolo Sim Não
PREPARE / DEALLOCATE Sim Nunca
ON COMMIT DROP tabelas temporárias Sim Sim
PRESERVE / DELETE ROWS tabelas temporárias Sim Nunca
Redefinição (reset) do plano em cache Sim Sim
comando LOAD Sim Nunca
241 11. Performance

Instalação do PgBouncer via código-fonte

Seguem abaixo detalhes e procedimentos para sua instalação via compilação de código-fonte.

Arquitetura de diretórios

Tipo Localização
Instalação /usr/local/pgbouncer/
Configuração /etc/pgbouncer
Binários /usr/local/pgbouncer/bin/

[#] Variáveis de ambiente de pacotes:

# Pacotes comuns
PKG='gcc make pkg-config'

# Pacotes Debian
PKG_DEB="libsystemd-dev libevent-dev libssl-dev"

[#] Instalação de pacotes e posterior limpeza de pacotes baixados:

apt update && apt install -y wget ${PKG} ${PKG_DEB} && apt clean

[#] Criação de usuário de sistema:

useradd \
-c 'PgBouncer system user' \
-s /usr/sbin/nologin \
-d /var/local/pgbouncer \
-g postgres \
-m -r pgbouncer &> /dev/null

[#] Criação de link para o diretório do pgbouncer:

ln -s ~pgbouncer /etc/
242 11. Performance

[#] Atribuição de variável de ambiente para versão completa do PogBouncer via prompt:

read -p \
'Número de versão completo (X.Y.Z) do PgBouncer a ser baixado: ' \
VERSION

[#] URL para baixar o PgBouncer:

URL="http://www.pgbouncer.org/downloads/files/\
${VERSION}/pgbouncer-${VERSION}.tar.gz"

[#] Baixar o PgBouncer para o diretório /tmp:

wget ${URL} -P /tmp/

[#] Acessar o diretório do código-fonte para a compilação:

# Acessar /tmp
cd /tmp

# Descompactar o arquivo
tar xvf pgbouncer-${VERSION}.tar.gz

# Acessar a pasta resultante


cd pgbouncer-${VERSION}

[#] Processo de configuração, com suporte a PAM, systemd e OpenSSL:

./configure \
--prefix /usr/local/pgbouncer \
--with-pam \
--with-systemd \
--with-openssl

[#] Compilação e instalação:

make && make install


243 11. Performance

[#] Criar arquivos de lista de usuários, de configuração e diretório de logs com permissões
somente para usuário e grupo:

# Arquivo de lista de usuários e de configuração


touch /etc/pgbouncer/{userlist.txt,pgbouncer.ini}

# Diretório de logs
mkdir /var/log/pgbouncer

# Permissão de diretórios
chmod 770 ~pgbouncer /etc/pgbouncer /var/log/pgbouncer

# Permissão de arquivos
chmod 660 /etc/pgbouncer/*

[#] Mudando usuário e grupo proprietários:

chown -R pgbouncer:postgres ~pgbouncer /var/log/pgbouncer /etc/pgbouncer

[#] Criar arquivo de usuários:

# Comando SQL que retorna o hash de senha do usuário


SQL="SELECT rolpassword FROM pg_authid WHERE rolname = 'aluno';"

# Criação de variável de ambiente capturando o hash do usuário


HASH_PWD=`su - postgres -c "psql -Atqc \"${SQL}\""`

# Fazendo uso da variável criada para inserir uma linha no arquivo


echo "\"aluno\" \"${HASH_PWD}\"" > /etc/pgbouncer/userlist.txt

O arquivo foi criado com o nome do usuário e seu hash de senha, cada um envolvido por
aspas.
244 11. Performance

[#] Criação de arquivo de configuração:

cat << EOF > /etc/pgbouncer/pgbouncer.ini


[databases]
* = host=127.0.0.1 port=5432

[pgbouncer]
listen_addr = 127.0.0.1
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
logfile = /var/log/pgbouncer/pgbouncer.log
admin_users = postgres
pool_mode = session
default_pool_size=90
max_client_conn=3000
EOF

[#] Criação do unit file do serviço do PgBouncer para o systemd:

cat << EOF > /etc/systemd/system/pgbouncer.service


[Unit]
Description=Connection pooler for PostgreSQL
Documentation=man:pgbouncer(1)
Documentation=https://www.pgbouncer.org/
After=network.target
#Requires=pgbouncer.socket

[Service]
Type=notify
User=pgbouncer
ExecStart=/usr/local/pgbouncer/bin/pgbouncer /etc/pgbouncer/pgbouncer.ini
ExecReload=/bin/kill -HUP \${MAINPID}
KillSignal=SIGINT
#LimitNOFILE=1024

[Install]
WantedBy=multi-user.target
EOF
245 11. Performance

[#] Criação de arquivo de limites (file descryptors):

cat << EOF > /etc/security/limits.d/pgbouncer.conf


pgbouncer soft nofile 30000
pgbouncer hard nofile 30000
EOF

[#] Habilitar e inicar o serviço imediatamente:

systemctl enable --now pgbouncer.service

[#] Remover pacotes instalados:

apt purge -y ${PKG} ${PKG_DEB}

[$] Criação de base de dados para testes:

createdb db_bench

[$] Inicialização do pgbench na base de dados criada:

pgbench -i db_bench

[$] Verificando o máximo de conexões disponíveis:

psql -Atqc 'SHOW max_connections;'

100

[$] Teste com pgbench, 500 conexões simultâneas e 10 transações cada:

pgbench -U aluno -c 500 -t 10 db_bench


246 11. Performance

. . .
pgbench: error: connection to database "db_bench" failed: FATAL: sorry, too many clients already
. . .
247 11. Performance

[$] Teste com pgbench, 500 conexões simultâneas e 10 transações cada, mas desta vez se
conectando no pgbouncer:

pgbench -U aluno -c 500 -t 10 -p 6432 db_bench

starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 1
query mode: simple
number of clients: 500
number of threads: 1
number of transactions per client: 10
number of transactions actually processed: 5000/5000
latency average = 1602.790 ms
tps = 311.956029 (including connections establishing)
tps = 311.959382 (excluding connections establishing)
248 11. Performance

OLTP (OnLine Transaction Processing)


Perfil de banco de dados transacional, com muito mais escrita do que consultas, cuja com-
plexidade são baixas.
Seu principal objetivo é processamento de dados.

Exemplos de aplicações OLTP:

• Transações bancárias;
• Sistemas de vendas online em geral;
• Sistemas de cadastros em geral etc.

Características

• Sistema transacional online;


• Gerencia modificações em bases de dados;
• É caracterizado por um grande número de pequenas transações online;
• Operações de escrita e leitura;
• Auxilia a controlar tarefas de negócios;
• Projetado para operações de negócio em tempo real;
• Muitos usuários;

Parametrizações para OLTP

WAL

Devido à sua escrita intensa, os logs de transação, podem virar um gargalo de performance.
É altamente aconselhável ter uma unidade de armazenamento dedicada.
Pode ser interessante alterar os parâmetros de configuração commit_siblings e commit_delay para
agrupar mais transações por fsync, assim diminuirá a carga de I/O que a escrita de WAL gera.
No entanto, se a aplicação tem uma natureza mais tranquila, com poucas transações, pode
gerar atraso nas efetivações.

Shared buffers

Também, devido à natureza de grandes escritas, ter um valor grande para shared_buffers,
de forma a poder armazenar o fluxo de dados que será gravado, é interessante para sempre
ter páginas disponíveis. Isso evita que backends precisem fazer as escritas por si mesmos.
A porcentagem de 25% a 40% da memória RAM para shared_buffers é uma recomendação
inicial.
249 11. Performance

Checkpoints

Há de se evitar que haja uma frequente ocorrência de checkpoints, então é altamente re-
comendável aumentar o parâmetro max_wal_size, cujo padrão é 1GB. De maneira similar, mas
por tempo, o parâmetro checkpoint_timeout, cujo valor padrão é 5min, pode ser aumentado
também.
O parâmetro log_checkpoints registra em log quando ocorre um checkpoint, então pode ser
muito útil para monitorar o quão frequente está.
250 11. Performance

OLAP (OnLine Analytical Processing)


Perfil de banco de dados analítico, onde há muito mais leitura do que escritas e suas consultas
são de uma complexidade alta.
Seu principal objetivo é análise de dados.
Qualquer sistema de datawarehouse (DW) / BI (business intelligence) é um sistema OLAP.

Algumas características próprias

• Sistema analítico online;


• Extrai insights dos dados;
• É caracterizado por poucas consultas, que geralmente levam muito tempo para fins de
análise;
• As escritas são geralmente feitas nas cargas, há muito mais leitura;
• Auxilia na tomada de decisões para o negócio;
• Poucos usuários;

Parametrizações no PostgreSQL

WAL

Aumentar a quantidade dos arquivos WAL vai beneficiar as cargas que serão feitas.

Memória

Diferente de OLTP, não há necessidade por grande quantidade de


shared_buffers. Bancos de dados OLAP se beneficiam do cache de sistema de arquivos
para leitura, mas, por outro lado, precisam de mais memória para operações de ordenação
- parâmetro work_mem, que sugerimos ajustar para 10% da memória RAM.
251 11. Performance

pgbench e benchmarks
O pgbench é um simples utilitário para rodar testes de benchmarks no PostgreSQL.
Ele roda a mesma sequência de comandos SQL de forma contínua, podendo ser de forma
concorrente, com várias conexões simultâneas. Então, calcula a média de taxa de transações
(transações por segundo).
Por padrão, o pgbench testa um cenário vagamente baseado no TPC-B, envolvendo cinco
comandos SELECT, UPDATE, and INSERT commands por transação.
Facilmente pode-se fazer testes personalizados, scripts SQL personalizados.
A base de dados do teste foi criada nos termos de um banco hipotético. O banco (financeiro)
tem um ou mais agências (branches). Cada agência tem múltiplos caixas de banco (tellers). O
banco tem muitos clientes, cada um com uma conta (accounts). A base de dados representa
o dinheiro em cada entidade (agência, caixa e conta) e um histórico das transações recentes
executadas pelo banco. A transação representa o trabalho executado quando um cliente faz
um depósito ou saque de sua conta. A transação é feita pelo caixa em alguma agência.

Melhores práticas com pgbench

Para evitar valores irreais utilizando o pgbench, seguem algumas dicas:

• Não executar o pgbench em um número pequeno de transações ou tempo. É re-


comendável que os testes sejam executados no mínimo por alguns minutos. Há casos
em que é necessário executar o teste por horas para conseguir resultados que possam
ser reproduzidos;

• Testar mais de uma vez para certificar-se de que o resultado não foi apenas um ruído;

• Inicializar os dados novamente pois o acúmulo de tuplas não vigentes e espaço reutilizável
podem mudar os resultados;

• Não executar com um número de clientes (-c) maior do que o fator de escala (-s
). Assim será medido somente contenção de atualização. O número de registros na
tabela pgbench_branches é igual ao fator de escala e toda transação tem uma atualização
nessa tabela; então, se o número de clientes exceder o fator de escala, teremos muitas
transações bloqueadas esperando por outras transações;

• O próprio pgbench pode se tornar um gargalo se o número de clientes for muito alto.
Execute o pgbench de outra máquina, que tenha uma baixa latência de rede.
Também pode ser útil executar várias instâncias do pgbench em diferentes máquinas.
252 11. Performance

[$] Caso exista, apagar a base de dados:

dropdb --if-exists db_bench

[$] Criar base de dados de teste:

createdb db_bench

[$] Inicialização do pgbench:

pgbench -i db_bench

dropping old tables...


NOTICE: table "pgbench_accounts" does not exist, skipping
NOTICE: table "pgbench_branches" does not exist, skipping
NOTICE: table "pgbench_history" does not exist, skipping
NOTICE: table "pgbench_tellers" does not exist, skipping
creating tables...
generating data (client-side)...
100000 of 100000 tuples (100%) done (elapsed 0.07 s, remaining 0.00 s)
vacuuming...
creating primary keys...
done in 0.28 s (drop tables 0.00 s, create tables 0.02 s, client-side generate 0.12 s, vacuum 0.06 s, primary

[$] Teste do pgbench com 50 conexões simultâneas, cada uma executando 100 transações:

pgbench -c 50 -t 100 db_bench

starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 1
query mode: simple
number of clients: 50
number of threads: 1
number of transactions per client: 100
number of transactions actually processed: 5000/5000
latency average = 129.741 ms
tps = 385.382845 (including connections establishing)
tps = 385.440467 (excluding connections establishing)
253 11. Performance

Visões materializadas
O comando CREATE MATERIALIZED VIEW define uma view materializada de uma consulta.
A consulta é executada e usada para popular a view na hora que o comando é dado (exceto se
for usado WITH NO DATA) e pode ser atualizada posteriormente utilizando REFRESH MATERIALIZED
VIEW.
CREATE MATERIALIZED VIEW é similar a CREATE TABLE AS, exceto que também lembra a consulta
usada para iniciar a view, de modo que possa ser atualizada posteriormente sob demanda.
Uma view materializada tem muitas das mesmas propriedades de uma tabela, mas não há
suporte para views materializadas temporárias ou geração automática de OIDs.
Sua grande vantagem com relação à uma view comum é o desempenho.
Uma visão materializada precisa do comando REFRESH para ter seus dados atualizados com
relação à tabela de origem.

Quando utilizar visões materializadas?

Visões materializadas por terem um desempenho melhor que as visões tradicionais acabam
sendo muito úteis em tabelas que sofrem gravações pontuais em que a sincronização com a
visão materializada se dará com o comando REFRESH.
Então as consultas da aplicação podem ser direcionadas para essa visão em vez da tabela ou
mesmo uma visão comum.

[>] Criação de tabela de teste:

CREATE TABLE tb_foo(


id_ int PRIMARY KEY
GENERATED BY DEFAULT AS IDENTITY,
numero int,
texto text);

[>] Popular a tabela com dois milhões de registros:

INSERT INTO tb_foo (numero, texto)


SELECT (random() * 1000000)::int,
substr(md5(generate_series(1, 2000000)::text), 1, 7);
254 11. Performance

[>] Criação de uma view comum:

CREATE VIEW vw_foo AS


SELECT numero, texto
FROM tb_foo
WHERE id_ % 2 = 0;

[>] Criação de view materializada:

CREATE MATERIALIZED VIEW mv_foo AS


SELECT numero, texto
FROM tb_foo
WHERE id_ % 2 = 0;

[>] Contagem de registros na view materializada:

SELECT count(*) FROM mv_foo;

count
---------
1000000

[>] Inserindo novos valores na tabela:

INSERT INTO tb_foo (numero, texto)


SELECT (random() * 1000000)::int,
substr(md5(generate_series(1, 1000000)::text), 1, 7);

[>] Contagem de registros na view comum:

SELECT count(*) FROM vw_foo;

count
---------
1500000
255 11. Performance

[>] Contagem de registros na view materializada:

SELECT count(*) FROM mv_foo;

count
---------
1000000

Nota-se, aqui, que há diferença entre ambos os tipos de views.


A view materializada necessita ser atualizada com o comando REFRESH.

[>] Atualizando a view materializada:

REFRESH MATERIALIZED VIEW mv_foo;

[>] Contagem de registros na view materializada:

SELECT count(*) FROM mv_foo;

count
---------
1500000

[>] Exibindo o plano de execução na view comum:

EXPLAIN ANALYZE
SELECT numero, texto FROM vw_foo;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Gather (cost=1000.00..37467.00 rows=15000 width=12) (actual time=6.658..583.693 rows=1500000 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Parallel Seq Scan on tb_foo (cost=0.00..34967.00 rows=6250 width=12) (actual time=0.010..139.185 rows=
Filter: ((id_ % 2) = 0)
Rows Removed by Filter: 500000
Planning Time: 0.069 ms
Execution Time: 665.537 ms
256 11. Performance

[>] Exibindo o plano de execução na view materializada:

EXPLAIN ANALYZE
SELECT numero, texto FROM mv_foo;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Seq Scan on mv_foo (cost=0.00..23109.00 rows=1500000 width=12) (actual time=0.048..89.577 rows=1500000 loops
Planning Time: 0.109 ms
Execution Time: 130.894 ms

O desempenho de busca com os mesmos critérios foi melhor na visão materializada.


Uma busca mais simples e rápida.
257 11. Performance

Tabelas não logadas - Unlogged tables


Uma tabela “unlogged” é uma tabela em que os dados não são escritos para o WAL (Write-
Ahead Log: logs de transação), o que faz com que fique consideravelmente mais rápida do
que uma tabela comum. Porém, não são seguras contra falhas (crash): uma tabela unlogged
é automaticamente truncada após uma falha ou um desligamento inapropriado.
O conteúdo de uma tabela não logada não é replicado para servidores standby.
Qualquer índice criado em uma tabela desse tipo é também “unlogged”, porém índices GiST
atualmente não são suportados.
É um tipo de tabela rápida, mas insegura. Não deve ser utilizada para operações que precisem
de persistência de dados.

Qual a utilidade de uma tabela não logada?

Tabelas não logadas são muito úteis quando não é importante a durabilidade dos dados. Por
exemplo: cache de dados, informações temporárias acessíveis a todas sessões abertas, cargas
ETL e etc.

[>] Criação da tabela não logada:

CREATE UNLOGGED TABLE tb_teste_unlogged (campo int);

[>] Criação de uma tabela comum:

CREATE TABLE tb_teste_logged (campo int);

[>] Ligar o cronômetro de comandos do psql:

\timing on
258 11. Performance

[>] Inserir três milhões de registros na tabela unlogged:

INSERT INTO tb_teste_unlogged (campo) SELECT generate_series(1, 3000000);

Time: 1168.419 ms (00:01.168)

[>] Inserir três milhões de registros na tabela comum:

INSERT INTO tb_teste_logged (campo) SELECT generate_series(1, 3000000);

Time: 3353.495 ms (00:03.353)

A diferença de tempo entre a tabela não logada e a tabela comum é percepível.

[$] Reiniciar o serviço do PostgreSQL no modo immediate:

pg_ctl -m i restart

Reinício do serviço de uma forma “não gentil”. . .

Aviso

Não utilizar o kill -9 ou killall -9, pois isso pode levar a uma corrupção de dados.
259 11. Performance

[>] De volta ao psql, verificar a tabela não logada:

TABLE tb_teste_unlogged LIMIT 5;

Sem registros!
A tabela foi truncada.

[>] No catálogo pg_class, utilizando de expressão regular que case com os nomes das tabelas
criadas, verificar o tipo de persistência:

SELECT
oid, relfilenode, relname, relpersistence, relkind
FROM pg_class
WHERE relname ~ '^tb_.*logged';

oid | relfilenode | relname | relpersistence | relkind


-------+-------------+-------------------+----------------+---------
25212 | 25212 | tb_teste_logged | p | r
25215 | 25215 | tb_teste_unlogged | u | r

Descrição das colunas:

oid: coluna de sistema que armazena o ID do objeto;


relfilenode: nome do arquivo em disco referente ao objeto, inicialmente é igual ao oid;
relname: nome do objeto;
relpersistence: tipo de persistência, sendo que p = permanente e u = unlogged;
relkind: tipo de relação, sendo que a mais comum é “r” para tabelas. Em uma tabela comum,
a persistência é um “p”, enquanto que uma tabela não logada é representada por um “u”. Ou
seja, se relpersistence for “u” é unlogged.

[>] Mudar tabela não logada para logada:

ALTER TABLE tb_teste_unlogged SET LOGGED;

[>] Mudar tabela logada para não logada:

ALTER TABLE tb_teste_logged SET UNLOGGED;


260 11. Performance

[>] Verificando o tipo de persistência e outros detalhes:

SELECT
oid, relfilenode, relname, relpersistence, relkind
FROM pg_class
WHERE relname ~ '^tb_.*logged';

oid | relfilenode | relname | relpersistence | relkind


-------+-------------+-------------------+----------------+---------
25215 | 25218 | tb_teste_unlogged | p | r
25212 | 25221 | tb_teste_logged | u | r

Quando uma tabela muda seu tipo de persistência de dados, ela tem que ser totalmente
reescrita, sendo necessário criar um novo arquivo. Isso explica a mudança de relfilenode após
alterar as tabelas.
261 11. Performance

Fillfactor - Fator de preenchimento


Fillfactoré um conceito aplicável para tabelas e índices, que é uma porcentagem correspon-
dente ao preenchimento de páginas para INSERTs e o restante destinado a UPDATEs.
Quando o valor do fator de preenchimento for menor que 100 (representa 100%), há um espaço
de páginas para alterações. E se esse espaço for suficiente, linhas modificadas se beneficiam
para poderem ser alocadas na mesma página da linha original, que é mais eficiente do que
alocar uma nova página.
Para tabelas, o valor padrão é 100; para índices, é de acordo com o tipo de índice.
Em suma, para tabelas que sofrem muitas alterações é recomendável que seu fator de preenchi-
mento seja menor.

Fillfactor em tabelas

[>] Habilita o temporizador de comandos do psql:

\timing on

Parte I -> Fillfactor = 100

[>] Criação da primeira tabela com fillfactor igual a 100 (cem):

CREATE TABLE tb_ff100(campo int);

[>] Inserindo um milhão de registros:

INSERT INTO tb_ff100 (campo) SELECT generate_series(1, 1000000);

Time: 883.443 ms

[>] Verificando o tamanho da tabela após o INSERT:

SELECT pg_size_pretty(pg_relation_size('tb_ff100'));

pg_size_pretty
----------------
35 MB
262 11. Performance

[>] Atualização de todos os registros:

UPDATE tb_ff100 SET campo = campo + 1;

Time: 1948.167 ms (00:01.948)

[>] Tamanho da tabela após o UPDATE:

SELECT pg_size_pretty(pg_relation_size('tb_ff100'));

pg_size_pretty
----------------
69 MB

[>] Apagar a tabela de fillfactor 100:

DROP TABLE tb_ff100;

Parte II -> Fillfactor = 50

[>] Criação da tabela com fillfactor igual a 50 (cinquenta):

CREATE TABLE tb_ff50(campo int) WITH (fillfactor = 50);

[>] Inserindo um milhão de registros:

INSERT INTO tb_ff50 (campo)


SELECT generate_series(1, 1000000);

Time: 1103.308 ms (00:01.103)

[>] Verificar o tamanho da tabela após o INSERT:

SELECT pg_size_pretty(pg_relation_size('tb_ff50'));
263 11. Performance

pg_size_pretty
----------------
69 MB

[>] Atualização de todos os registros:

UPDATE tb_ff50 SET campo = campo + 1;

Time: 1301.622 ms (00:01.302)

[>] Tamanho da tabela após o UPDATE:

SELECT pg_size_pretty(pg_relation_size('tb_ff50'));;

pg_size_pretty
----------------
69 MB

[>] Remover a tabela de teste de fillfactor 50:

DROP TABLE tb_ff50;

Resumo dos testes

Fillfactor 100 50
INSERT Inicial (ms) 883.443 1103.308
Tamanho da tabela após INSERT (MB) 35 69
UPDATE (ms) 1948.167 1301.622
Tamanho da tabela após UPDATE (MB) 69 69
264 11. Performance

Fillfactor em índices

Parte I -> Fillfactor = 100

[>] Criação da tabela de testes para fillfactor de índice 100%:

CREATE TABLE tb_ff100(campo int);

[>] Criação do índice com fillfactor igual a 100:

CREATE INDEX idx_ff100


ON tb_ff100 (campo)
WITH (fillfactor = 100);

[>] Verificar informações sobre o índice criado para a tabela:

\d tb_ff100

Table "public.tb_ff100"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
campo | integer | | |
Indexes:
"idx_ff100" btree (campo) WITH (fillfactor='100')

[>] Inserindo um milhão de registros:

INSERT INTO tb_ff100 (campo) SELECT generate_series(1, 1000000);

Time: 2157.771 ms (00:02.158)

[>] Verificando o tamanho do índice após o INSERT:

SELECT pg_size_pretty(pg_relation_size('idx_ff100'));

pg_size_pretty
----------------
19 MB
265 11. Performance

[>] Atualização de todos os registros:

UPDATE tb_ff100 SET campo = campo + 1;

4517.604

[>] Verificando o tamanho do índice após o UPDATE:

SELECT pg_size_pretty(pg_relation_size('idx_ff100'));

pg_size_pretty
----------------
39 MB

[>] Apagar a tabela de fillfactor 100:

DROP TABLE tb_ff100;

Parte II -> Fillfactor = 50

[>] Criação da tabela de testes para fillfactor de índice de 50%:

CREATE TABLE tb_ff50(campo int);

[>] Criação do índice com fillfactor igual a 50:

CREATE INDEX idx_ff50 ON tb_ff50 (campo)


WITH (fillfactor = 50);
266 11. Performance

[>] Verificando nas informações de estrutura da tabela as informações sobre o índice criado
para ela:

\d tb_ff50

Table "public.tb_ff50"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
campo | integer | | |
Indexes:
"idx_ff50" btree (campo) WITH (fillfactor='50')

[>] Inserindo um milhão de registros:

INSERT INTO tb_ff50 (campo)


SELECT generate_series(1, 1000000);

Time: 2597.165 ms (00:02.597)

[>] Verificando o tamanho do índice após o INSERT:

SELECT pg_size_pretty(pg_relation_size('idx_ff50'));

pg_size_pretty
----------------
39 MB

[>] Atualização de todos os registros:

UPDATE tb_ff50 SET campo = campo + 1;

Time: 3648.516 ms (00:03.649)

[>] Verificando o tamanho do índice após o UPDATE:

SELECT pg_size_pretty(pg_relation_size('idx_ff50'));

pg_size_pretty
----------------
267 11. Performance

39 MB

[>] Remover a tabela de teste de fillfactor 50:

DROP TABLE tb_ff50;

Resumo dos testes

Fillfactor 100 50
INSERT Inicial (ms) 2157.771 2597.165
Tamanho do índice após INSERT (MB) 19 39
UPDATE (ms) 4517.604 3648.516
Tamanho da índice após UPDATE (MB) 39 39
268 11. Performance

Plano de execução
Plano de execução é o caminho utilizado para executar um comando.

O caminho de uma consulta


Segue abaixo uma visão geral resumida dos estágios que uma consulta passa para obter um
resultado:

Conexão -> Parser -> Rewriter -> Planner -> Executor

Conexão

Uma conexão de uma aplicação para o servidor PostgreSQL tem que ser estabelecida. A
aplicação transmite uma consulta para o servidor e aguarda seu resultado.

Parser (analisador)

No estágio do parser (analisador), ocorre uma checagem da consulta transmitida pela apli-
cação, verificando se a sintaxe está correta. Então, cria-se uma árvore de consulta.

Rewrite system (sistema de reescrita)

Toma a árvore de consulta criada pelo parser e procura por quaisquer regras (armazenadas em
catálogos do sistema) para aplicá-las à árvore de consulta. Transformações são executadas de
acordo com os corpos das regras.
Um exemplo de utilização do sistema de reescrita é em views. Sempre que uma consulta é
feita em uma view, a consulta é reescrita de forma a acessar diretamente a(s) tabela(s) de sua
consulta de construção.

Planejador / otimizador de consultas

É o responsável pela interpretação dos comandos emitidos e por determinar qual é método de
execução mais eficiente.
Ele pega a árvore de consulta (reescrita) e cria um plano de consulta que será a entrada do
executor.
Primeiro, ele cria todos os caminhos possíveis que levam ao mesmo resultado.
Por exemplo, se houver um índice em uma tabela para ser buscado, há dois caminhos para
procurar. Um possivelmente é uma simples busca sequencial e outra possibilidade é fazer
uso do índice. O custo da execução de cada caminho é estimado e o que for mais barato é
escolhido.
269 11. Performance

O caminho menos custoso é expandido dentro de um plano completo que o executor pode
usar.
Para que o planejador de consultas do PostgreSQL tome melhores decisões para executar um
determinado comando, os dados de pg_statistic devem estar atualizados para todas as tabelas
envolvidas. O daemon autovacuum normalmente já faz isso de forma automática, mas se
uma tabela tem mudanças substanciais feitas recentemente, vai ser preciso fazer um ANALYZE
manual em vez de esperar um autovacuum para pegar essas mudanças. A quantidade de cache
que está em níveis abaixo do PosgreSQL, como cache de sistemas de arquivos, cache de con-
troladoras, cache de armazenamento e a própria memória RAM são fundamentais. Somando
todos esses, configura-se o parâmetro effective_cache_size. Assim, o planejador realizará o
cálculo de probabilidade de um determinado dado estar ou não em cache.
Com o parâmetro effective_io_concurrency, definimos a quantidade de unidades de armazena-
mento para execução de operações paralelas. Com essa informação, o planejador poderá
executar operalções de I/O assíncronas paralelamente quando se utilizar bitmap heap scans.

Executor

O executor percorre recursivamente a árvore do plano de consulta e traz as linhas no caminho


representado pelo plano.
O executor faz uso do sistema de armazenamento enquanto busca tabelas, faz ordenações e
junções, avalia qualificações e finalmente devolve as linhas derivadas.

O comando EXPLAIN

O comando EXPLAIN exibe informações do plano de execução de um determinado comando.


Dentre essas informações ele traz: tipo de busca, custos, tempos de planejamento e execução,
quantas linhas o comando retorna, quantos laços (loops) etc.
Dentre suas opções, ANALYZE, que aqui é apenas uma opção do comando EXPLAIN, o que não
tem o mesmo efeito do comando SQL ANALYZE, faz com que o comando seja efetivamente
executado e não apenas planejado.
Sendo assim, ao executar um EXPLAIN com ANALYZE, se o comando dado for de escrita (e. g.
INSERT, UPDATE, DELETE) e deseja-se apenas fazer testes sem modificações, então isso deve ser
feito dentro de uma transação.
https://www.postgresql.org/docs/current/sql-explain.html
270 11. Performance

[>] Criação de tabela de teste:

SELECT
generate_series(1, 2000000) AS campo
INTO tb_foo;

[>] Criação de um índice comum na tabela:

CREATE INDEX idx_teste_comum


ON tb_foo (campo);

[>] Criação de um índice parcial na tabela para valores divisíveis por 19:

CREATE INDEX idx_teste_div19


ON tb_foo (campo)
WHERE campo % 19 = 0;

[>] Plano de execução sem ANALYZE de uma consulta:

EXPLAIN
SELECT campo FROM tb_foo
WHERE campo = 975421;

QUERY PLAN
-----------------------------------------------------------------------------------
Index Only Scan using idx_teste_comum on tb_foo (cost=0.43..4.45 rows=1 width=4)
Index Cond: (campo = 975421)

[>] Plano de execução com ANALYZE de uma consulta:

EXPLAIN ANALYZE
SELECT campo FROM tb_foo
WHERE campo = 975421;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Index Only Scan using idx_teste_comum on tb_foo (cost=0.43..4.45 rows=1 width=4) (actual time=0.105..0.107 r
Index Cond: (campo = 975421)
Heap Fetches: 0
Planning Time: 0.073 ms
Execution Time: 0.126 ms
271 11. Performance

[>] Plano de execução sem ANALYZE de uma consulta com números divisíveis por 19:

EXPLAIN
SELECT count(*) FROM tb_foo
WHERE campo % 19 = 0;

QUERY PLAN
-----------------------------------------------------------------------------------------------
Aggregate (cost=287.42..287.43 rows=1 width=8)
-> Index Only Scan using idx_teste_div19 on tb_foo (cost=0.42..262.42 rows=10000 width=0)

[>] Plano de execução com ANALYZE de uma consulta com números divisíveis por 19:

EXPLAIN ANALYZE
SELECT count(*) FROM tb_foo
WHERE campo % 19 = 0;

QUERY PLAN
-----------------------------------------------------------------------------------------------
Aggregate (cost=287.42..287.43 rows=1 width=8) (actual time=16.956..16.957 rows=1 loops=1)
-> Index Only Scan using idx_teste_div19 on tb_foo (cost=0.42..262.42 rows=10000 width=0) . . .
Heap Fetches: 0
Planning Time: 0.084 ms
Execution Time: 16.986 ms

[>] Plano de execução com ANALYZE, formato JSON, modo verboso, informação de buffers
para uma consulta com números divisíveis por 19:

EXPLAIN (ANALYZE, FORMAT JSON, VERBOSE, BUFFERS)


SELECT count(*) FROM tb_foo
WHERE campo % 19 = 0;

QUERY PLAN
--------------------------------------------
[ +
{ +
"Plan": { +
"Node Type": "Aggregate", +
"Strategy": "Plain", +
"Partial Mode": "Simple", +
"Parallel Aware": false, +
"Startup Cost": 287.42, +
"Total Cost": 287.43, +
"Plan Rows": 1, +
"Plan Width": 8, +
"Actual Startup Time": 16.420, +
"Actual Total Time": 16.421, +
"Actual Rows": 1, +
"Actual Loops": 1, +
. . .
272 11. Performance

[>] Remover a tabela de teste:

DROP TABLE tb_foo;

Parêmetros do planejador de consultas

Custos do planejador

É possível alterar os custos estimados para cada operação dos planos de execução. Os custos
utilizam uma medida arbitrária, como se fosse uma moeda instituída para determinar custos
de operações.

• seq_page_cost(floating point)
Custo estimado de leitura de página em disco para páginas sequenciais.

• random_page_cost (floating point)


Custo estimado de leitura de página em disco para páginas não sequenciais.
Reduzi-lo em relação a seq_page_cost fará com que a busca em índices seja preferida.
Aumentar ambos fará com que se prefira o uso de CPU.
Só faz sentido utilizar um random_page_cost igual ou menor a seq_page_cost para sistemas
que utilizam bancos de dados inteiramente em memória, ou com cache muito grande.

• (floating point)
cpu_tuple_cost
Custo estimado de processamento por linha de uma consulta.

• (floating point)
cpu_index_tuple_cost
Custo estimado de processamento por entrada de índice.

• (floating point)
cpu_operator_cost
Custo estimado de processamento por operador ou função executada durante uma con-
sulta.

• effective_cache_size(integer)
Permite ao planejador estimar o tamanho efetivo do cache de disco disponível para uma
consulta.
Aumentar este valor fará com que o planejador prefira buscas em índices. Considere que
o cache disponível pode ser o próprio cache do PostgreSQL mais o cache do sistema
operacional.
Este valor não faz nenhuma alocação de memória, apenas diz ao planejador uma esti-
mativa de memória disponível.
273 11. Performance

Métodos de busca

Para se efetuar buscas nas tabelas o planejador do PostgreSQL utiliza um dos determinados
métodos de busca que serão vistos a seguir.
Cada método tem sua utilidade, sendo que sua aplicação depende de fatores como a própria
consulta feita e outros.

Sequential scan

Varredura sequencial na tabela.


Se os dados selecionados de uma tabela corresponderem à maior parte ou se na ausência de
índice, será utilizada a busca sequencial.

Index scan

A varredura é feita conforme um índice da consulta que aponta para as linhas que contenham
o valor que satisfaça o critério de busca.
Esse tipo de busca, quando é possível, acaba sendo muito mais rápido que a busca sequencial,
pois vai direto onde estão os valores.

Index only scan

Seu funcionamento é parecido com index scan, se diferencia justamente como seu nome indice:
a varredura é feita apenas na estrutura de dados de índices.

Bitmap scan

Esse tipo de busca é uma mistura da busca sequencial e a busca por índice, na qual se tenta
resolver a desvantagem da busca por índice, mas mantém as vantagens.

TID scan

Busca for TID é um tipo de varredura muito específica do PostgreSQL onde no critério de
busca é usado um campo desse tipo, como a coluna de sistema ctid por exemplo.

Parallel scan

Tipo de varredura feito com auxílio de paralelização, em que o trabalho é dividido entre núcleos
de CPU.

Paralelização de execução

A paralelização de execuções iniciou-se no 9.6 e desde então a cada versão mais recursos
ganharam suporte à paralelização.
274 11. Performance

Muitas consultas podem rodar duas ou mais vezes mais rápidas quando se usa paralelização.
O uso de query paralelizadas pode aumentar o consumo de CPU do servidor.
Para se fazer uso de paralelismo, o otimizador de consultas avalia os seguintes parâmetros de
configuração:

• dynamic_shared_memory_type
O valor não pode ser nulo, pois consultas paralelas precisam de memória compartilhada
dinâmica para passar os dados entre os processos. No Linux, seu valor costuma ser
posix.

• parallel_setup_cost
Define a estimativa de custo do planejador para lançar um processo worker paralelo.

• parallel_tuple_cost
Define a estimativa de custo do planejador para transferir uma tupla de um processo
worker para outro.

• min_parallel_table_scan_size
Determina o tamanho mínimo dos dados de uma tabela que devem ser varridos para
uma busca paralela ser considerada.

• min_parallel_index_scan_size
Define o tamanho mínimo de dados de índice que deve ser vasculhado para uma busca
paralela ser considerada.

• max_worker_processes
Configura a quantidade máxima de processos background que o sistema pode suportar.

• max_parallel_workers_per_gather
Determina a quantidade máxima de workers que podem ser iniciados por um único nó
Gather ou Gather Merge.
Os workers paralelos são retirados do pool de processos estabelecido por
max_worker_processes, limitado por max_parallel_workers.
Nota-se que o número solicitado de workers pode não estar realmente disponível no
tempo de execução. Se isso ocorrer, o plano será executado com menos workers do que
o esperado, o que pode ser ineficiente.
Definir seu valor como 0 desativa a execução de consulta paralela.
275 11. Performance

Otimizador de busca de algoritmo genético

Quando se tem uma certa quantidade de joins muito grande, não há o que possa ser feito
pelo planejador de consultas para poder avaliá-las exaustivamente e ainda retornar planos de
execução em um tempo razoável.
Em situações como essa é que o otimizador de consultas genético (genetic query optimizer -
GEQO) atua.
É feita a numeração de cada tabela que faz parte da junção começando por 1.
O GEQO começa criando alguns desses planos aleatoriamente, então eles são avaliados com
relação às suas adequações, principalmente com relação ao custo de execução de cada um.
Desses planos, os melhores são mantidos e os piores descartados. Algumas mudanças são
feitas, mutações, e o processo se repete por algumas gerações.
Devido à aleatoriedade desse processo, não se deve esperar que os planos gerados pelo GEQO
sejam os mesmos.
É possível controlar isso para ter planos mais consistentes, tornando fixa a semente aleatória
e, então, ter sempre o mesmo valor.
276 11. Performance

——————– EXTRA ——————–

Huge pages
O gerenciamento de memória é feito em blocos (páginas). Por padrão, cada bloco tem 4096
bytes (4kb). Sendo assim, um 1MB terá 256 blocos (1 MB = 1024kb; 1024 / 4 = 256).
Um processo, ao utilizar uma porção de memória, faz com que a CPU marque a RAM usada
por esse processo. Para ser mais eficiente, a CPU aloca por blocos de memória e também
para swap.
Endereços de processos são virtuais, então, a CPU e o sistema operacional têm que saber qual
página pertence a qual processo. Quanto mais páginas tiver, mais tempo levará para encontrar
o mapeamento. Por exemplo, um processo que usa 1GB de memória tem 262144 entradas
para procurar (1 GB / 4 kb).
Atualmente, a maior parte das arquiteturas de CPU suportam páginas maiores, o que faz com
que CPU e SO tenham menos entradas para procurar, assim obtendo mais desempenho.
No Linux, o nome é huge pages, em BSDs super pages e no Windows large pages.
Resumindo, huge pages são uma forma de fazer o sistema operacional aperfeiçoar o gerencia-
mento de grandes quantidades de memória por meio do aumento de tamanho dos blocos. Esse
aumento de tamanho de blocos pode ser para 2MB ou 1GB, dependendo da CPU utilizada,
em vez dos “míseros” 4kb. Sendo assim, qual utilizar quando disponível? De maneira geral,
para escalar e gerenciar memória, 2MB são ideais para gigabytes de memória enquanto 1GB
melhores para terabytes de memória.
Menos buscas na tabela de blocos implica em maior performance.
A utilização de huge pages reduz o overhead quando se acessa grandes pedaços contíguos de
memória, no PostgreSQL, para grandes valores de shared_buffers.
Nas flags de CPU pode-se encontrar quanto é suportado para aumentar o tamanho de uma
página, sendo que se houver a flag pse = 2MB e pdpe1gb = 1GB.
Para habilitar huge pages em um servidor PostgreSQL, é necessário fazer modificações no sis-
tema operacional (parâmetros de kernel) e habilitar o parâmetro huge_pages do postgresql.conf,
que é um parâmetro enumerado que aceita três valores: off, on e try. Respectivamente
desabilita, habilita e tenta utilizar huge pages caso esteja disponível no sistema operacional.
Caso não estiver, o serviço vai iniciar sem erro.
277 11. Performance

Transparent Huge Pages (THP)

É um sistema de gerenciamento de memória do Linux para criar huge pages de forma trans-
parente no Linux.
Para sistemas gerenciadores de bancos de dados, é um recurso que pode degradar a perfor-
mance. Cargas em bancos de dados costumam ter acessos à memória mais esparsos do que
padrões contíguos.
É extremamente recomendável para servidores de bancos de dados terem esse recurso desabil-
itado para maior desempenho.
Há um processo do kernel que roda em segundo plano, o khugepaged, que tenta criar huge
pages a partir de blocos contíguos de memória encontrados, então convertendo-os em uma
huge page. A forma como o khugepaged trabalha acaba sendo muito onerosa computacional-
mente e pode causar paralisações, picos de latência em determinadas situações e as páginas
são travadas enquanto ele as estiver manipulando.

[#] Instalar o utilitário bc para cálculos em linha de comando e apagar pacotes baixados em
seguida:

apt install -y bc && apt clean

[#] Verificar as flags de CPU e filtrar por cor diferente caso encontrar suporte a huge pages:

fgrep flags /proc/cpuinfo | head -1 | egrep --color=always 'pse|pdpe1gb'

flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36
clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt rdtscp lm constant_tsc
rep_good nopl nonstop_tsc cpuid extd_apicid tsc_known_freq pni pclmulqdq ssse3 cx16
sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx rdrand hypervisor lahf_lm cmp_legacy
cr8_legacy abm sse4a misalignsse 3dnowprefetch ssbd vmmcall fsgsbase avx2 rdseed
clflushopt arat

No retorno do comando destacou-se apenas pse para este servidor, indicando que o mesmo só
tem suporte a huge pages de 2M.
278 11. Performance

[#] Verificar se o recurso THP está habilitado no sistema operacional:

cat /sys/kernel/mm/transparent_hugepage/enabled

[always] madvise never

THP habilitado.

[#] Editar o arquivo de configuração do grub, de forma a desabilitar THP e configurar o


tamanho de huge pages para 2MB. Logo em seguida, aplica essas configurações para a próxima
vez que o servidor iniciar:

sed 's:\(GRUB_CMDLINE_LINUX=".*\)":\
\1 transparent_hugepage=never hugepagesz=2M default_hugepagesz=2M":g' \
-i /etc/default/grub && update-grub2

[$] Verificar o parâmetro huge_pages do PostgreSQL:

psql -Atqc 'SHOW huge_pages;'

try

[$] Criar uma base de dados de teste:

createdb db_bench

[$] Criar dados com o pgbench utilizando fator de escala 100:

pgbench -i -s 100 db_bench

[$] Com o pgbench, simular um ambiente com 50 conexões durante 15 minutos:


279 11. Performance

pgbench -c 50 -T 900 db_bench


280 11. Performance

[$] PID principal do PostgreSQL para a variável de ambiente:

export PGPID=`head -1 ${PGDATA}/postmaster.pid`

[$] Arquivo temporário para armazenar o pico de utilização de memória em kilobytes:

echo `egrep ^VmPeak /proc/${PGPID}/status | awk '{print $2}'` > ~/vmpeak.tmp

[#] Reiniciar o servidor:

init 6

[#] Após conectar-se novamente, verificar se o recurso THP está habilitado no sistema op-
eracional:

cat /sys/kernel/mm/transparent_hugepage/enabled

always madvise [never]

THP desabilitado.

[#] Total de huge pages desejadas (pico + 20%):

export HUGE_PAGES_TOTAL_KB=\
`echo "\`cat ~postgres/vmpeak.tmp\` * 1.2" | bc | cut -f1 -d.`

Os 20% a mais é para fins de garantia.

[#] Variável de ambiente para tamanho de uma HugePage::

export HUGEPAGESIZE=`fgrep Hugepagesize /proc/meminfo | awk '{print $2}'`


281 11. Performance

[#] Quantidade de huge pages armazenada na variável de ambiente:

export NR_HUGEPAGES=`echo "${HUGE_PAGES_TOTAL_KB} / ${HUGEPAGESIZE}" | bc`

A quantidade de huge pages é conseguida ao dividirmos o total desejado pelo tamanho de


uma huge page.

[#] Modificações em parâmetros de kernel via sysctl:

# Número de huge pages:


echo "vm.nr_hugepages = ${NR_HUGEPAGES}" >> /etc/sysctl.d/pgsql.conf

# ID do grupo de sistema postgres, que tem autorização para usar huge pages:
echo "vm.hugetlb_shm_group = `id -g postgres`" >> /etc/sysctl.d/pgsql.conf

# Aplicar as mofificações
sysctl -p /etc/sysctl.d/pgsql.conf

[#] Modificações de limites:

cat << EOF | tr ' ' '\t' >> /etc/security/limits.d/pgsql.conf


postgres hard memlock ${HUGE_PAGES_TOTAL_KB}
postgres soft memlock ${HUGE_PAGES_TOTAL_KB}
EOF

[$] Habilitar o parâmetro huge_pages em postgresql.conf:

sed 's:\(#huge_pages.*\):huge_pages = on:g' -i ${PGDATA}/postgresql.conf

[$] Reiniciar o serviço do PostgreSQL e remover o arquivo temporário criado:

pg_ctl restart && rm ~/vmpeak.tmp

[$] Verificar o parâmetro huge_pages do PostgreSQL:

psql -Atqc 'SHOW huge_pages;'


282 11. Performance

on
12
Observabilidade
• Observabilidade
• Logs
• Análise de logs com pgbadger
• Monitoramento pontual
• Monitoramento contínuo
• Catálogos e views de sistema
• Colunas de sistema
• O Módulo pg_stat_statements

283
284 12. Observabilidade

Observabilidade
É a capacidade de fazer inferências em um sistema a partir de suas saídas para saber de suas
condições internas, trazendo informações importantes como o quê, onde e por quê.
A observabilidade de um sistema auxilia para que haja monitorado, pois para isso é preciso,
antes de mais nada, ser observável.
Métricas, rastreamento e logs são a base da observabilidade.

Métricas

São o alicerce do monitoramento, valores que têm certas informações a respeito de um estado
interno de um sistema. Métricas, habitualmente, são determinadas como números, medidas
ou quantidades que são coletados dentro de um período. Métricas informam coisas como
quanto de disco foi consumido para uma tarefa que gerou arquivos temporários, quanto de
memória foi utilizada para um processamento etc.

Rastreamento

Rastreamento conta com detalhes o caminho de uma solicitação que vai de uma máquina para
outra em um sistema distribuído. Por meio do rastreamento, podem ser vistos efeitos não
planejados de uma solicitação e a perceptibilidade da estrutura dela. Rastreios possibilitam
detalhamento de determinadas solicitações a fim de definir os componentes que acarretaram
erros de sistema, assim, monitorando a fluência entre os componentes e descobrindo gargalos
de performance.

Logs

Com logs devidamente configurados, pode-se encontrar um comportamento inadequado do


sistema e identificar a razão ao analisar um problema. É interessante que logs sejam gerados
para serem utilizados / lidos tanto por seres humanos como por máquinas, com uma estrutura
definida, em formatos como CSV, JSON ou YAML, por exemplo. Assim, sistemas de análise
de logs podem processá-los e facilitar consultas posteriores.

Monitoramento

Utilizando um conjunto previamente determinado de métricas, monitorar possibilita que se


observe e compreenda o estado de um sistema, cujo objetivo é detectar ou se antecipar falhas.
Isso evita incidentes ou, na pior das situações, envia um alerta do que ocorreu.
285 12. Observabilidade

Logs
São registros de atividades de sistema de suma importância para administradores, sejam eles
DBAs ou sys admins.
Através dos logs, podemos observar se as coisas estão fluindo normalmente e, caso não estejam,
por meio dos registros de atividades de sistema é possível
tomar uma ação e corrigir o que não estiver conforme o esperado.

Configurações de logs no PostgreSQL

A seguir serão vistos alguns parâmetros de log conforme sua categoria.


O intuito é destacar os mais utilizados / importantes. Para maiores informações, consulte a
documentação oficial:

https://www.postgresql.org/docs/current/runtime-config-logging.html

Onde registrar

• log_destination
Método para registrar as mensagens do servidor. Parâmetro que aceita os seguintes
valores: stderr (padrão), csvlog e syslog.
Pode-se escolher mais de um valor, declarando-os entre vírgulas.

• logging_collector
Habilita o logging collector, que é um processo background que captura mensagens de
log enviadas e as redireciona para arquivos de log.

• log_directory
Diretório de logs.

• log_filename
String que determina o padrão do nome de arquivo de log.

• log_rotation_age
Depende de logging_collector estar habilitado. Determina quanto tempo vai durar o
arquivo de log até ele ser rotacionado. Essa rotação para outro arquivo só acontecerá
se log_filename for um padrão dinâmico e não fixo, o qual no seu padrão faz menção ao
tempo.

• log_rotation_size
Mesma ideia de log_rotation_age, mas com a rotação de arquivos baseada no tamanho
286 12. Observabilidade

e não no tempo.
• log_truncate_on_rotation
Quando habilitado faz com que o arquivo de log seja sobrescrito em vez de as linhas
novas serem simplesmente adicionadas.

Quando registrar

• log_min_messages
Nível de mensagem que será registrada no log.
Valores válidos são DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, INFO, NO-
TICE, WARNING (padrão), ERROR, LOG, FATAL, e PANIC.
Cada nível inclui todos os níveis que o seguem e quanto mais avançado for, menos
mensagens serão enviadas para o log.

• log_min_error_statement
Controla se comandos SQL que causam uma condição de erro são registrados em log.
Então, o comando é incluído na entrada de log para qualquer mensagem da severidade
ou maior.
Valores válidos são DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, INFO, NO-
TICE, WARNING, ERROR (padrão), LOG, FATAL, e PANIC.

• log_min_duration_statement
Tempo mínimo de duração de um comando em que a partir desse tempo de duração o
comando é registrado em log. Ajustando seu valor para Zero, registrará todos comandos
dados, enquanto que se for ajustado para -1, o desabilita.

O que registrar

• application_name
String customizável para identificar o nome da aplicação que se conecta.

• log_checkpoints
Registra ou não em log quando ocorre um checkpoint.

• log_connections
Registra ou não em log quando ocorre uma conexão.

• log_disconnections
Registra ou não em log quando ocorre uma desconexão.

• log_duration
Faz com que a duração de cada comando completado seja registrada em log.
287 12. Observabilidade

• log_error_verbosity
Nível de detalhe a ser escrito em log.
Valores válidos: TERSE, DEFAULT, e VERBOSE.
Cada um adiciona mais campos para serem exibidos nas mensagens.

• log_hostname
Habilitando este parâmetro faz com que se registre o hostname também.
Dependendo da resolução de nome, isso pode causar uma sensível perda de performance.

• log_line_prefix
É um padrão em string que determina como é o início de cada linha de log.

• log_lock_waits
Registrar ou não quando em uma sessão houver uma espera maior que deadlock_timeout
para conseguir uma trava.
Muito útil para determinar se essas esperas estão causando um deterioramento de per-
formance.

• log_statement
Que tipo de comandos SQL são registrados em log.
Valores válidos: none (desabilitado), ddl, mod, e all (todos comandos).

• log_replication_commands
Faz com que cada comando de replicação seja registrado em log.

• log_temp_files
Controla se registra o uso de arquivos temporários e seus respectivos tamanhos.
Zero registra todos e -1 desabilita este parâmetro.

• log_timezone
Configura o timezone (fuso horário) usado para escrever os logs.

• log_autovacuum_min_duration
Registra a execução do autovacuum
288 12. Observabilidade

[$] Editar o postgresql.conf e alterar os parâmetros conforme segue:

vim ${PGDATA}/postgresql.conf

log_destination = 'stderr,csvlog'
log_directory = '/var/log/pgsql/<Versão Majoritária>'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_min_duration_statement = 0

[$] Verificar o contexto dos parâmetros alterados:

psql -Atqc "


SELECT
DISTINCT context
FROM pg_settings
WHERE name IN (
'log_destination',
'log_directory',
'log_filename',
'log_min_duration_statement');
"

sighup
superuser

Um simples reload é suficiente para aplicar as modificações no postgresql.conf.

[$] Reload no serviço do PostgreSQL:

pg_ctl reload

[$] Variável de ambiente para a versão majoritária do PostgreSQL:

read -p 'Digite o número da versão majoritária do PostgreSQL: ' PGMAJOR


289 12. Observabilidade

[$] Listar os arquivos de log:

ls -1 /var/log/pgsql/${PGMAJOR}/

. . .

postgresql-2021-07-21_112620.csv
postgresql-2021-07-21_112620.log

Note que há arquivos .log e .csv (log_destination = 'stderr,csvlog')

[>] Dentro do psql dê os seguintes comandos:

SELECT 1;
SELECT 'foo';
SELECT 5 + 2;

[$] Exibir as últimas três linhas do último arquivo de log .log:

ls -1 /var/log/pgsql/${PGMAJOR}/*.log | tail -1 | xargs -i tail -F {}

2021-07-21 11:27:15.859 -03 [6357] LOG: duration: 0.329 ms statement: SELECT 1;


2021-07-21 11:27:15.860 -03 [6357] LOG: duration: 0.093 ms statement: SELECT 'foo';
2021-07-21 11:27:16.697 -03 [6357] LOG: duration: 7.429 ms statement: SELECT 5 + 2;

[$] Exibir as últimas três linhas do último arquivo de log .csv::

ls -1 /var/log/pgsql/${PGMAJOR}/*.csv | tail -1 | xargs -i tail -F {}

. . .

Foi observado que o conteúdo é o mesmo, a diferença é a formatação, o último em CSV.


Observa-se também que os comandos dados foram registrados em log
(log_min_duration_statement = 0).
290 12. Observabilidade

Análise de logs com pgbadger


O pgbager é um analisador de logs do PostgreSQL com relatórios detalhados e gráficos.
É uma ferramenta muito útil para obter maior performance a partir de conclusões que se
possam ter dos relatórios.
Seu funcionamento é bem simples: ele é executado procurando logs em um determinado
diretório e, a partir desses logs, gera um relatório em formato HTML.
Para coletar as informações e gerar os relatórios, é preciso configurar os logs do PostgreSQL.
Para sua coleta ser efetiva, é necessário que se modifiquem determinados parâmetros de
configuração de log.

[#] Instalar o PgBadger:

apt install -y pgbadger

[$] Editar o postgresql.conf e alterar os parâmetros conforme segue:

vim ${PGDATA}/postgresql.conf

log_line_prefix = '%t [%p]: user=%u,db=%d,app=%a,client=%h '


log_checkpoints = on
log_connections = on
log_disconnections = on
log_lock_waits = on
log_temp_files = 0
log_autovacuum_min_duration = 0
log_min_duration_statement = 0
log_error_verbosity = default
lc_messages = 'en_US.UTF-8'
291 12. Observabilidade

[$] Verificar o contexto dos parâmetros alterados:

psql -Atqc "


SELECT
DISTINCT context
FROM pg_settings
WHERE name IN (
'log_line_prefix',
'log_checkpoints',
'log_connections',
'log_disconnections',
'log_lock_waits',
'log_temp_files',
'log_autovacuum_min_duration',
'log_error_verbosity',
'lc_messages');
"

sighup
superuser
superuser-backend

Um simples reload é suficiente para aplicar as modificações no postgresql.conf.

[$] Reload no serviço do PostgreSQL:

pg_ctl reload

[$] Criação de base de dados de teste:

createdb db_pgbadger

[$] Inicialização de dados do pgbench:

pgbench -i -s 20 db_pgbadger
292 12. Observabilidade

[$] Executa o pgbench por pouco mais de 5 minutos para gerar volume de logs:

pgbench -c 90 -T 530 db_pgbadger

starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 1
query mode: simple
number of clients: 90
number of threads: 1
duration: 530 s
number of transactions actually processed: 184227
latency average = 259.026 ms
tps = 347.454849 (including connections establishing)
tps = 347.456221 (excluding connections establishing)

[$] Rodar o pgbadger usando paralelismo para gerar o relatório em formato HTML:

pgbadger -j `nproc` -o /tmp/report.html `psql -Atqc 'SHOW log_directory;'`/*

O pgbager com a opção -o determina o arquivo HTML que será o relatório. Como último
parâmetro, há o diretório de logs com todos seus arquivos.

[$] Trazer o relatório gerado de outra máquina:

scp postgres@<ip_do_servidor_PostgreSQL>:/tmp/report.html /tmp/

Abra o arquivo HTML em um navegador.

Esse é o relatório gerado pelo pgbadger através dos logs do PostgreSQL.


293 12. Observabilidade

Monitoramento pontual
É fundamental que se conheça as principais ferramentas de monitoramento pontual, bem
como suas métricas para detectar problemas e gargalos. Assim, é possível tomar as devidas
providências.
A seguir, serão vistas ferramentas de S. O. a serem utilizadas para mensurar consumo de
recursos do PostgreSQL.

Ferramentas de monitoramento de consumo de recursos

top

Utilitário muito popular em sistemas operacionais da família Unix. Fornece uma visão dinâmica
em tempo real do sistema, exibe informações sumarizadas, bem como uma lista de processos
ou threads que estão sendo gerenciados pelo kernel do Linux.

[#] Execução do top:

top

top - 11:47:04 up 1 min, 1 user, load average: 0.03, 0.02, 0.00


Tasks: 78 total, 1 running, 77 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 973.3 total, 771.6 free, 45.5 used, 156.3 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 768.2 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 103732 10036 7988 S 0.0 1.0 0:01.28 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
3 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_gp
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_par_gp
. . .

Acima, temos uma amostra de uma tela do top, que é composta por um cabeçalho com
informações de recursos. Após esse cabeçalho, consta a lista detalhada de processos que estão
rodando no momento.

Informações do cabeçalho:

• load average
A carga média do sistema, a média da quantidade de processos aguardando na fila de
execução (run queue) mais a quantidade de processos em execução, contidos no último
período de 1, 5 e 15 minutos.
Quando um desses números for muito maior que a quantidade de núcleos de CPU,
294 12. Observabilidade

significa que há uma grande carga no sistema e isso pode causar problemas.

• Tasks
Quantidade de processos.

• %Cpu(s) Uso de CPU


– us, user: tempo de execução de processos de usuário
– sy, system: tempo de execução de processos do kernel
– ni, nice: tempo de execução de processos de usuário com nice
– id, idle: tempo gasto no manipulador ocioso do kernel
– wa, IO-wait: tempo de espera para conclusão de I/O
– hi: tempo gasto em manutenção de interrupções de hardware
– si: tempo gasto em manutenção de interrupções de software
– st: tempo roubado desta vm pelo hipervisor

• MiB Mem
Uso de memória RAM.

• MiB Swap
Uso de swap.

Na listagem de processos temos as colunas relativas aos processos:

• PID: número de identificação;


• USER: usuário;
• PR: prioridade;
• NI: valor de nice;
• VIRT: uso de memória virtual;
• RES: uso de memória residente, quanto da memória física o processo está usando;
• SHR: uso de memória compartilhada;
• S: estado (status) do processo;
• %CPU: porcentagem de uso de CPU;
• %MEM: porcentagem de uso de memória
• TIME+: tempo de CPU, com mais granularidade, exibindo centésimos de segundo;
• COMMAND: linha de comando que gerou o processo.
295 12. Observabilidade

htop

O htop é um top melhorado graficamente, pois, apesar de ser em modo texto, apresenta suas
medições CPU, memória RAM e swap em pequenas barras verticais coloridas.

ps

O utilitário ps exibe informações sobre uma seleção dos processos ativos.

[#] Com a opção -F (extra full format), listar os processos do usuário postgres:

ps -Fu postgres

UID PID PPID C SZ RSS PSR STIME TTY TIME CMD


postgres 714 1 0 52465 24788 0 16:45 ? 00:00:00 /usr/local/pgsql/13/bin/postgres -
D /var/local/pgsql/data/13
postgres 715 714 0 16035 4564 0 16:45 ? 00:00:00 postgres: logger
postgres 717 714 0 52495 5728 0 16:45 ? 00:00:00 postgres: checkpointer
postgres 718 714 0 52465 5648 0 16:45 ? 00:00:00 postgres: background writer
postgres 719 714 0 52465 9860 0 16:45 ? 00:00:00 postgres: walwriter
postgres 720 714 0 52603 8128 0 16:45 ? 00:00:00 postgres: autovacuum launcher
postgres 721 714 0 16056 4600 0 16:45 ? 00:00:00 postgres: stats collector
postgres 722 714 0 52574 6452 0 16:45 ? 00:00:00 postgres: logical replication launcher

[#] Com a opção -F (extra full format), listar os processos do comando postgres:

ps -FC postgres

UID PID PPID C SZ RSS PSR STIME TTY TIME CMD


postgres 714 1 0 52465 24788 0 16:45 ? 00:00:00 /usr/local/pgsql/13/bin/postgres -
D /var/local/pgsql/data/13
postgres 715 714 0 16035 4564 0 16:45 ? 00:00:00 postgres: logger
postgres 717 714 0 52495 5728 0 16:45 ? 00:00:00 postgres: checkpointer
postgres 718 714 0 52465 5648 0 16:45 ? 00:00:00 postgres: background writer
postgres 719 714 0 52465 9860 0 16:45 ? 00:00:00 postgres: walwriter
postgres 720 714 0 52603 8128 0 16:45 ? 00:00:00 postgres: autovacuum launcher
postgres 721 714 0 16056 4600 0 16:45 ? 00:00:00 postgres: stats collector
postgres 722 714 0 52574 6452 0 16:45 ? 00:00:00 postgres: logical replication launcher

A listagem igual ao comando anterior se deve por causa do processo principal do PostgreSQL
ter o mesmo nome do usuário; postgres.

vmstat

O vmstat é um programa em linha de comando que reporta estatísticas de memória, paginação,


CPU e I/O de uma maneira bem sucinta.
296 12. Observabilidade

[#] Informações de vmstat com intervalo de 5 segundos:

vmstat 5

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----


r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 822112 12404 104940 0 0 14 0 18 49 0 0 100 0 0

A primeira coleta (primeira linha) deve ser descartada, pois ela não representa a real situação
do sistema. Diante disso, sempre é necessário informar uma taxa de atualização das coletas,
que neste caso foi de 5 segundos. Caso contrário, ele apresentará somente a primeira coleta.

Descrição:

• procs
– r: quantidade de processos executáveis (em execução ou aguardando o tempo de
execução);
– b: o número de processos em hibernação ininterrupta.
• memory
– swpd: quantidade de memória virtual usada;
– free: quantidade de memória ociosa;
– buff: quantidade de memória usada como buffers;
– cache: quantidade de memória usada como cache;
– inact: quantidade de memória inativa (opção -a);
– active: quantidade de memória ativa (opção -a).
• Swap
– si: quantidade de memória trocada do disco (/s);
– so: quantidade de memória trocada para o disco (/s).
• IO
– bi: blocos recebidos de um dispositivo de bloco (blocos/s);
– bo: blocos enviados para um dispositivo de bloco (blocos/s).
• System
– in: o número de interrupções por segundo, incluindo o relógio;
– cs: o número de mudanças de contexto por segundo.
• CPU
– us: tempo gasto executando código não kernel (tempo de usuário, incluído tempo
nice);
– sy: tempo gasto executando o código do kernel (tempo de sistema);
– id: tempo gasto inativo;
– wa: tempo gasto esperando por IO;
– st: tempo roubado de uma máquina virtual.
297 12. Observabilidade

No uso do “vmstat”, destacam-se as seguintes informações:

• Fluxo de uso de swap


Através das colunas si e so, é possível saber a quantidade de dados fluindo da memória
para swap, ou vice-versa. É com estes indicadores que se determina se o “swap” está
sendo efetivamente usado e atrapalhando no desempenho.

• Número de trocas de contexto Apresentado na coluna “cs”, este valor tende a subir
quando se aumenta o número de processos.

iostat

O iostat é uma ferramenta usada para monitorar I/O de dispositivos de armazenamento e


consumo de CPU relacionado.

[#] Instalação do pacote sysstat e posterior limpeza:

apt install -y sysstat && apt clean

[#] Estatísticas do dispositivo /dev/sda com intervalo de 3 segundos e contagem indetermi-


nada:

iostat /dev/sda 3

Linux 5.7.2 (debian) 03/03/2021 _x86_64_ (1 CPU)

avg-cpu: %user %nice %system %iowait %steal %idle


0.09 0.00 0.10 0.03 0.00 99.79
Device tps kB_read/s kB_wrtn/s kB_read kB_wrtn
sda 0.72 27.47 3.18 199926 23144
avg-cpu: %user %nice %system %iowait %steal %idle
0.00 0.00 0.00 0.00 0.00 100.00

[#] Estatísticas extendidas do dispositivo /dev/sda com intervalo de 3 segundos e contagem


indeterminada:

iostat -x /dev/sda 3

Linux 5.7.2 (debian) 03/03/2021 _x86_64_ (1 CPU)


avg-cpu: %user %nice %system %iowait %steal %idle
298 12. Observabilidade

0.09 0.00 0.10 0.03 0.00 99.79


Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm
sda 0.56 0.16 27.20 3.15 0.32 0.32 36.31 66.69 . . .
299 12. Observabilidade

No modo detalhado, destacam-se as seguintes informações:

• Tamanho médio da fila


Apresentado na coluna avgqu-sz, é média no tamanho da fila de IO no tempo mensurado.

• Média de tempo de espera para concluir uma requisição


Apresentado na coluna await, é a média de tempo gasto do início de uma requisição até
receber o retorno do dispositivo.

• Média de tempo de serviço


Apresentado na coluna svctm, é a média de tempo gasto a partir da saída da requisição
do S.O. até seu retorno. Pode ser considerado como tempo de resposta do dispositivo.

• Porcentagem de utilização
Apresentado na coluna %util, traz uma aproximação do uso do disco quando atinge
valores próximos a 100%. Pode-se dizer que o disco está saturado.

Aviso

Apesar de ser útil para determinar problemas de desempenho em disco, a métrica de “tempo
de serviço” não é mais confiável no Linux e será removida das próximas versões do iostat.

A métrica de porcentagem de utilização %util não é confiavel para dispositivos que processam
requisições paralelas, como discos SSD, discos que utilizam RAID e quando temos storage
conectado com fibra e multipath.

iotop

O iotop é similar ao top, mas suas informações são de I/O por processo.
Extremamente útil para localizar qual processo está causando uma grande carga de I/O.

[#] Instalação do pacote e posterior limpeza:

apt install -y iotop && apt clean


300 12. Observabilidade

[#] Execução do iotop:

iotop

Total DISK READ: 0.00 B/s | Total DISK WRITE: 0.00 B/s
Current DISK READ: 0.00 B/s | Current DISK WRITE: 0.00 B/s
TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
1 be/4 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % systemd --switched-root --system --
deserialize 18
2 be/4 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % [kthreadd]
3 be/0 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % [rcu_gp]
4 be/0 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % [rcu_par_gp]
6 be/0 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % [kworker/0:0H-kblockd]
8 be/0 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % [mm_percpu_wq]
9 be/4 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % [ksoftirqd/0]
10 be/4 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % [rcu_sched]
. . .

pg_top

Outra ferramenta similar ao top, sendo que esta é dedicada para exibir apenas sobre processos
PostgreSQL.

[#] Instalação do pacote e posterior limpeza:

apt install -y pgtop && apt clean

[$] Execução do pg_top:

pg_top

. . .
PID USERNAME PRI NICE SIZE RES STATE TIME WCPU CPU COMMAND
718 postgres 20 0 205M 5648K sleep 0:00 0.00% 0.00% postgres: background writer
719 postgres 20 0 205M 9860K sleep 0:00 0.00% 0.00% postgres: walwriter
. . .

[$] Verificando estatísticas de pacotes com netstat:

netstat -s -p tcp | fgrep segments

63115 segments received


44410 segments sent out
301 12. Observabilidade

10 segments retransmitted
0 bad segments received

Nota-se que houve retransmissão de pacotes (10 segments retransmitted), o que significa que
houve perda de pacotes.
302 12. Observabilidade

Monitoramento contínuo
O monitoramento pontual é muito útil para extrair informações rápidas e imediatas, mas o
monitoramento contínuo permite ter histórico.
O histórico de um monitoramento contínuo é muito importante para se extrair conclusões
(insights) conforme o comportamento de algo monitorado ao longo do tempo.
O histórico de monitoramento permite também investigar melhor questões de resolução de
problemas (troubleshooting) e performance.
Há soluções de monitoramento contínuo open source com interface web que facilitam muito
a visualização de dados através de seus gráficos. Também há outras funcionalidades, como
alertas para evitar ou avisar a respeito de incidentes, por exemplo:

• Prometheus / Grafana;

• Zabbix.

SAR

O SAR (System Activity Report, em português Relatório de Atividade do Sistema) é um


utilitário já bem conhecido em sistemas operacionais da família Unix.
No Linux, ele é fornecido com o pacote sysstat.
Utilizando o sar pode-se monitorar para fins de performance itens como CPU, memória e
armazenamento em tempo real.
Ele pode coletar dados de forma contínua, armazenar e fazer análise para identificar gargalos.
As informações que ele coleta são vindas dos diretórios /proc e /sys. Para fazer uso do sar, é
necessário executar um dos utilitários: sadc ou sa1 para que seja feita a coleta períodica dos
dados. Executá-los pode ser manualmente ou via cron.
Ao instalar, o pacote sysstat é instalado como serviço e roda um dos dois (dependendo da
distribuição Linux) na inicialização da máquina. Então, as informações são gravadas em um
arquivo de log em formato binário e o sar faz a leitura desse tipo de arquivo.

O log binário tem os seguintes formatos, um somente dia atual, o outro data completa:

• /var/log/sysstat/saDD (prioridade)
• /var/log/sysstat/saYYYYMMDD

[#] Após a instalação do pacote sysstat, habilitar sua inicialização:

sed 's/ENABLED="false"/ENABLED="true"/g' -i /etc/default/sysstat


303 12. Observabilidade

[#] Reiniciar o serviço sysstat:

systemctl restart sysstat.service

[#] Execução do sar:

sar -A

Linux 5.7.2 (debian) 03/04/2021 _x86_64_ (1 CPU)


08:52:55 PM LINUX RESTART (1 CPU)

Nota-se que não há informações para analisar.

[#] Apagar quaisquer logs do sysstat que existirem:

rm -f /var/log/sysstat/*

[#] Criar uma coleta com intervalo de 1 segundo e 50 amostras no formato YYYYMMDD:

/usr/lib/sysstat/sadc 1 50 /var/log/sysstat/sa`date +%Y%m%d`

[#] Nova execução do sar:

sar -A

Linux 5.7.2 (debian) 03/04/2021 _x86_64_ (1 CPU)


08:59:25 PM CPU %usr %nice %sys %iowait %steal %irq %soft %guest %gnice
08:59:26 PM all 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
08:59:26 PM 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
. . .

Após a geração do log binário, o utilitário sar pode exibir os dados coletados.
304 12. Observabilidade

[$] 10 coletas, cada uma a cada 2 segundos:

sar 2 10

Linux 5.7.2 03/04/2021 _x86_64_ (1 CPU)


09:01:43 PM CPU %user %nice %system %iowait %steal %idle
09:01:45 PM all 0.00 0.00 0.00 0.00 0.00 100.00
09:01:47 PM all 0.00 0.00 0.00 0.00 0.00 100.00
09:01:49 PM all 0.00 0.00 0.00 0.00 0.00 100.00
09:01:51 PM all 0.00 0.00 0.00 0.00 0.00 100.00
09:01:53 PM all 0.00 0.00 0.00 0.00 0.00 100.00
. . .
Average: all 0.00 0.00 0.00 0.00 0.00 100.00

[$] 1 coleta, 1 segundo, modo de impressão em tela pretty, por cada dispositivo de armazena-
mento:

sar -p -d 1 1

Linux 5.7.2 (debian) 03/04/2021 _x86_64_ (1 CPU)


09:03:05 PM DEV tps rkB/s wkB/s areq-sz aqu-sz await svctm %util
09:03:06 PM sda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: sda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
305 12. Observabilidade

Catálogos e views de sistema


Catálogos

Os catálogos de sistema são os locais onde o sistema gerenciador de banco de dados armazena
os metadados, tais como informações sobre tabelas e colunas, além de informações de controle
interno.
O sistema de catálogos do PostgreSQL são tabelas regulares.
É possível apagar e recriar as tabelas, adicionar colunas, inserir e alterar valores, e claro, dan-
ificar seu sistema dessa forma.
Normalmente, não se deve alterar os catálogos do sistema manualmente, há sempre os co-
mandos SQL para fazer isso. (Por exemplo, CREATE DATABASE insere uma linha no catálogo
pg_database e cria o banco de dados na hora em disco).
Há algumas exceções para operações particularmente esotéricas (incomuns), como a adição
de métodos de acesso de índice.
A maior parte dos catálogos de sistema é copiada do banco de dados modelo (template) du-
rante a criação de uma base de dados. Depois disso, um banco de dados específico.
Alguns poucos catálogos são fisicamente compartilhados através de todas bases de dados em
um cluster ; esses são observados nas descrições dos catálogos individuais.
Os catálogos do PostgreSQL estão nos schemas pg_catalog e information_schema.
https://www.postgresql.org/docs/current/static/catalogs.html

[>] Listando catálogos:

SELECT
schemaname||'.'||relname AS catalogo
FROM pg_stat_sys_tables
WHERE schemaname IN ('pg_catalog', 'information_schema')
ORDER BY schemaname, relname;

catalogo
--------------------------------------------
information_schema.sql_features
information_schema.sql_implementation_info
information_schema.sql_parts
. . .

Views de sistema

Em adição aos catálogos, o PostgreSQL oferece uma série de views embutidas (built in).
Algumas views de sistema fornecem acesso facilitado para algumas consultas em catálogos do
sistema.
Outras views oferecem acesso ao estado interno do servidor.
306 12. Observabilidade

O esquema de informações (information_schema) oferece um conjunto alternativo de views que


sobrepõe funcionalidades de views de sistema.
Uma vez que o esquema de informações é padrão SQL, ao passo que as views aqui descritas
são específicas do PostgreSQL, geralmente é melhor usar o esquema de informações se ele
fornece todas as informações que você precisa.
Há algumas views adicionais que fornecem acesso a resultados do coletor de estatísticas.
Exceto quando mencionado, todas as views descritas aqui são somente leitura.

https://www.postgresql.org/docs/current/static/views-overview.html

[>] Listando visões de sistema:

SELECT
n.nspname||'.'||c.relname AS view_de_sistema
FROM pg_class AS c
INNER JOIN pg_namespace AS n
ON (c.relnamespace = n.oid)
WHERE n.nspname IN ('pg_catalog', 'information_schema')
AND c.relkind = 'v'
ORDER BY random();

view_de_sistema
----------------------------------------------------------
information_schema.parameters
pg_catalog.pg_stat_wal_receiver
pg_catalog.pg_stat_sys_indexes
. . .

Views estatísticas

O PostgreSQL tem estatísticas de utilização de objetos e recursos, que são coletadas pelo
coletor de estatísticas e disponibilizadas em forma de views de sistema.
Essas visões (views) estão no schema pg_catalog e têm sua nomenclatura iniciada por pg_stat
ou pg_statio, sendo que esse último tipo são para estatísticas de I/O de objetos.
Dentre essas views:

• pg_stat_activity
Uma linha por processo, exibindo informações relativas à atividade atual do processo;

• pg_stat_bgwriter
Sempre terá uma única linha, a qual exibe informações globais da instância a respeito
do bgwriter ;

• pg_stat_database
307 12. Observabilidade

Uma linha por base de dados e para objetos compartilhados exibindo suas estatísticas;

• pg_stat_(all,user,sys)_tables
Informações estatísticas de tabelas.

• pg_stat_(all,user,sys)_indexes
Informações estatísticas de índices.

• pg_statio_(all,user,sys)_tables
Informações estatísticas de I/O de tabelas.

• pg_statio_(all,user,sys)_indexes
Informações estatísticas de I/O de índices.

• pg_statio_(all,user,sys)_sequences
Informações estatísticas de I/O de sequências.

Information Schema

Em SGBDs relacionais, o Information Schema (information_schema) é um schema (namespace)


padrão que contém views e tabelas que fornecem informações de objetos sobre a base de dados
atual.

https://www.postgresql.org/docs/current/static/information-schema.html

[>] Criação de tabela de teste:

CREATE TABLE tb_foo(


id_ int PRIMARY KEY
GENERATED BY DEFAULT AS IDENTITY,
campo1 int2,
campo2 int4,
campo3 int8,
campo4 text);
308 12. Observabilidade

[>] Obtendo informações da tabela criada via information_schema:

SELECT column_name, data_type


FROM information_schema.columns
WHERE table_name = 'tb_foo'
AND table_schema = 'public';

column_name | data_type
-------------+-----------
id_ | integer
campo1 | smallint
campo2 | integer
campo3 | bigint
campo4 | text

[>] Remover a tabela de teste:

DROP TABLE tb_foo;


309 12. Observabilidade

——————– EXTRA ——————–

Colunas de sistema
• oid: identificador de objeto (object ID) de uma linha
Está presente apenas se a tabela foi criada usando WITH OIDS, seu tipo também se chama
oid.

• tableoid: OID da tabela que contém a linha


Facilita consultas que selecionam hierarquias herdadas, uma vez sem, é difícil dizer de
qual linha individual de uma tabela ela vem.
A coluna tableoid pode ser usada para fazer junção com a coluna oid do catálogo pg_class
para obter o nome da tabela.

• xmin: ID de transação de inserção


Uma versão de linha é um estado individual de uma linha; cada atualização de uma linha
cria uma nova versão para a mesma linha lógica.

• cmin:
Identificador de comando
Começa por zero dentro da transação de inserção.

• xmax: ID de transação) da transação de deleção


É possível que esta coluna seja diferente de zero em uma versão de linha visível.
Isso geralmente indica que a transação excluída ainda não foi confirmada ou que uma
tentativa de exclusão foi revertida.

• cmax:Identificador do comando na transação de exclusão


O identificador do comando na transação de exclusão ou zero.

• ctid: Localização física da linha internamente em sua tabela


Apesar de poder ser usada para localizar a versão da linha rapidamente, uma ctid de
uma linha mudará se for atualizada ou movida por um VACUUM FULL. Portanto, ctid é
inútil como identificador de linha de longo prazo. O oid ou mesmo um número serial
definido por usuário deve ser usado para identificar linhas lógicas.
Essa coluna é do tipo tid (tuple identifier ), identificador de linha de 6 bytes.
Esse tipo é composto de um par de valores; o número do bloco (4 bytes) e o índice
da tupla (2 bytes) dentro do bloco que identifica a localização física da linha dentro da
tabela.
310 12. Observabilidade

[>] Criação de tabela de teste:

CREATE TABLE tb_foo(


id_ int PRIMARY KEY
GENERATED BY DEFAULT AS IDENTITY,
cor text,
qtd int2);

[>] Inserir dados:

INSERT INTO tb_foo (cor, qtd) VALUES


('azul', 3),
('preto', 4),
('verde', 7);

[>] Selecionar ctid e as colunas declaradas:

SELECT ctid, * FROM tb_foo;

ctid | id_ | cor | qtd


-------+-----+-------+-----
(0,1) | 1 | azul | 3
(0,2) | 2 | preto | 4
(0,3) | 3 | verde | 7

[>] Selecionar ctid e as colunas declaradas:

UPDATE tb_foo SET qtd = 70 WHERE cor = 'verde';

[>] Selecionar ctid e as colunas declaradas:

SELECT ctid, * FROM tb_foo;

ctid | id_ | cor | qtd


-------+-----+-------+-----
(0,1) | 1 | azul | 3
(0,2) | 2 | preto | 4
(0,4) | 3 | verde | 70

Nota-se que o valor de ctid para o id 3 mudou.


311 12. Observabilidade

[>] Aplicar um VACUUM FULL na tabela:

VACUUM FULL tb_foo;

[>] Selecionar ctid e as colunas declaradas:

SELECT ctid, * FROM tb_foo;

ctid | id_ | cor | qtd


-------+-----+-------+-----
(0,1) | 1 | azul | 3
(0,2) | 2 | preto | 4
(0,3) | 3 | verde | 70

[>] Remover a tabela de teste:

DROP TABLE tb_foo;


312 12. Observabilidade

——————– EXTRA ——————–

O Módulo pg_stat_statements
O módulo pg_stat_statements fornece meios de acompanhar estatísticas de execução de todos
comandos SQL executados no servidor.
Este módulo deve ser carregado adicionando pg_stat_statements ao parâmetro shared_preload_libraries
no postgresql.conf, porque ele precisa de memória compartilhada adicional e é necessário um
restart no serviço.
Ao carregar esse módulo, ele rastreia estatísticas através de todas as bases do servidor.
Para acessar e manipular essas estatísticas, é fornecida uma view de mesmo nome e as funções
pg_stat_statements_reset e pg_stat_statements.

https://www.postgresql.org/docs/current/static/pgstatstatements.html

Configurações do pg_stat_statements (nome - tipo - contexto)

• - integer - postmaster
pg_stat_statements.max
Quantidade máxima de comandos rastreados pelo módulo.

• - enum - superuser
pg_stat_statements.track
Define que comandos serão contados.
– top: comandos top-level, são feitos diretamente pelos clientes;
– all: todos comandos;
– none: desabilita.

• - boolean - superuser
pg_stat_statements.track_utility
Define se comandos de utilidade (SELECT, INSERT, UPDATE e ‘DELETE) são rastreados.

• - boolean - superuser
pg_stat_statements.track_planning
Controla se operações de planejamento e duração são rastreadas. Habilitando este
parâmetro, pode resultar em uma perceptível perda de performance.

• pg_stat_statements.save- boolean - sighup


Especifica se deixa salvas as estatísticas coletadas.
Quando habilitado, este parâmetro mantém o que foi salvo mesmo que o serviço for
reiniciado.
313 12. Observabilidade

[$] Criar diretório extra de configuração:

mkdir ${PGDATA}/conf.d

[$] Criar arquivo de configuração do pg_stat_statements:

cat << EOF > ${PGDATA}/conf.d/pg_stat_statements.conf


# pg_stat_statements Settings
pg_stat_statements.max = 10000
pg_stat_statements.track = all
pg_stat_statements.track_utility = on
pg_stat_statements.track_planning = off
pg_stat_statements.save = on
EOF

[$] Editar o postgresql.conf:

vim ${PGDATA}/postgresql.conf

shared_preload_libraries = 'pg_stat_statements'
include_dir = 'conf.d'

[#] Variável de ambiente para versão majoritária do PostgreSQL:

read -p \
'Digite a versão majoritária: ' \
PGMAJOR

[#] Reiniciar o serviço do PostgreSQL:

systemctl restart postgresql-${PGMAJOR}

[$] Criar uma base de dados para teste de performance:

createdb db_bench
314 12. Observabilidade

[$] Habilitar a extensão na base:

psql -Atqc 'CREATE EXTENSION pg_stat_statements;' db_bench

[$] Dar reset nos dados de pg_stat_statements:

psql -Atqc 'SELECT pg_stat_statements_reset();' db_bench

[$] Inicialização da base de teste de desempenho com o pg_bench:

pgbench -i db_bench

[$] Testes com o pgbench:

pgbench -c10 -t300 db_bench

[$] Consultar estatísticas:

psql -d db_bench -qc "


SELECT
query,
calls,
total_exec_time,
rows, 100.0 * shared_blks_hit /
nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
FROM pg_stat_statements
ORDER BY total_exec_time DESC LIMIT 5;
"

. . .

De acordo com o resultado, pode-se notar a relação com os testes do pgbench.

[$] Remover a base de dados para teste:

dropdb db_bench
13
Particionamento de tabelas
• O que é particionamento de tabelas
• Particionamento por faixa de valores (RANGE)
• Particionamento por lista enumerada (LIST)
• Particionamento por hash
• Particionamento multinível

315
316 13. Particionamento de tabelas

O que é particionamento de tabelas


Particionar uma tabela significa dividí-la em subtabelas (partições), de forma que o que seria
uma tabela enorme, será distribuído entre suas partições. Na declaração de particionamento,
inclui-se o método de particionamento além de uma lista de colunas ou expressões a serem
usadas como chave de particionamento (partition key). Essa chave deve ser única.
A tabela particionada funciona como um roteador, recebe os dados ou requisições e redire-
ciona para a partição pertinente, pois ela mesma não tem armazenamento próprio, mas sim
suas partições. Uma tabela particionada é uma tabela virtual. Cada partição armazena um
subjconjunto de dados conforme definido por seus limites de partição.
Quando se apaga a tabela particionada, suas partições também são removidas.
Particiona-se uma tabela para resolver problemas relativos a grandes volumes de dados con-
centrados em uma única tabela.
Não é possível tornar uma tabela comum em uma tabela particionada ou vice versa. Com o
comando ALTER TABLE ... ATTACH PARTITION pode-se adicionar uma tabela comum como partição
a uma tabela particionada. E com ALTER TABLE ... DETACH PARTITION, é possível desassociar uma
partição, transformando-a em uma tabela comum.

Dividir para conquistar!

https://www.postgresql.org/docs/current/ddl-partitioning.html

Benefícios do particionamento de tabelas

• Desempenho
Tabelas são representadas fisicamente por data files. Se uma tabela é particionada,
não será mais somente um arquivo. Assim, em consultas cujos dados estejam em uma
determinada partição, sua leitura ou escrita serão mais rápidas por não precisar ler blocos
de partes que não sejam pertinentes.
Os índices também serão menores, pois, ao criar um índice para uma tabela particionada,
na verdade serão vários índices variando de quantidade conforme o número de partições,
ou seja, cada partição terá seu próprio índice.

• Remoções de dados
A operação de DELETE tem alto custo, pois produz tuplas mortas e continua ocupando
espaço em disco.
Se uma certa quantidade de dados for exatamente de acordo com o critério de uma
partição, pode-se utilizar ALTER TABLE ... DETACH PARTITION ou um simples DROP TABLE na(s)
partição(es), o que faz com que se evite overhead por conta de uma operação de vacuum.
317 13. Particionamento de tabelas

• Redução de custo
Dados usados raramente podem ser migrados para mídias de armazenamento mais
baratas e mais lentas.

• Manutenção
Para operações de VACUUM ou ANALYZE, em vez de ser executado em uma grande tabela,
será em suas partições. Dependendo da quantidade de memória, o tamanho de uma
partição pode caber dentro dela, assim, agilizando todo o processo.

• Travas (locks)
Redução de locks graças a um certo grau de autonomia que as partições têm.

Quando particionar uma tabela?

• Conteúdo da tabela precisa ser distribuído em dispositivos diferentes conforme seu uso;
• Muitos problemas com locks;
• Desempenho degradado;
• Quando consultas cujas condições trazem um conjunto específico de dados em vez de
percorrer toda tabela;
• Tabelas de log ou de histórico;
• Tabelas cujos tamanhos superam a memória RAM;
• Como ponto de partida de tamanho, tabelas a partir de 20GB devem ser consideradas.

Partição padrão (default)

É a partição definida para pegar valores que não combinam com quaisquer outras partições.

Partition pruning

Partition pruning é o termo utilizado para descrever a escolha de um plano pelo planejador de
consultas em que a consulta satisfaz o critério de particionamento de uma tabela, de forma a
evitar ler partições desnecessárias da tabela particionada. Assim reduzindo I/O significativa-
mente.
O parâmetro de configuração enable_partition_pruning, que é booleano, define se esse recurso
é habilitado ou não. Por padrão, é habilitado.
318 13. Particionamento de tabelas

Dicas e boas práticas de particionamento

Padronização

Crie as partições seguindo um padrão de forma que futuras partições possam ser criadas
dinamicamente obedecendo um critério.
Por exemplo, uma tabela que se chama tb_vendas e que será particionada por faixas de meses:
tb_vendas_201901, tb_vendas_201902, tb_vendas_201903. . .

Namespaces (schemas)

Crie schemas próprios para as partições, pois assim facilitará a visualização das tabelas exis-
tentes.
É sempre bom organizar objetos e, conforme dito anteriormente, faça isso de forma padronizada.
319 13. Particionamento de tabelas

Particionamento por faixa de valores (RANGE)


A tabela é particionada por intervalos de valores, sendo que nesse intervalo o formato é “[)”;
fechado no início e aberto no final.

[>] Tabela particionada por intervalo:

CREATE TABLE tb_intervalo (


id_ int
GENERATED BY DEFAULT AS IDENTITY,
dt date,
PRIMARY KEY (id_, dt))
PARTITION BY RANGE (dt);

Na chave primária, é necessário ter o campo dt, que é critério de particionamento, assim
formando a chave de particionamento.

[>] Partições para receber dados somente do ano de 2019:

CREATE TABLE tb_intervalo_2019_01


PARTITION OF tb_intervalo
FOR VALUES FROM ('2019-01-01') TO ('2019-02-01');

CREATE TABLE tb_intervalo_2019_02


PARTITION OF tb_intervalo
FOR VALUES FROM ('2019-02-01') TO ('2019-03-01');

CREATE TABLE tb_intervalo_2019_03


PARTITION OF tb_intervalo
FOR VALUES FROM ('2019-03-01') TO ('2019-04-01');

CREATE TABLE tb_intervalo_2019_04


PARTITION OF tb_intervalo
FOR VALUES FROM ('2019-04-01') TO ('2019-05-01');

CREATE TABLE tb_intervalo_2019_05


PARTITION OF tb_intervalo
FOR VALUES FROM ('2019-05-01') TO ('2019-06-01');

CREATE TABLE tb_intervalo_2019_06


PARTITION OF tb_intervalo
FOR VALUES FROM ('2019-06-01') TO ('2019-07-01');
320 13. Particionamento de tabelas

CREATE TABLE tb_intervalo_2019_07


PARTITION OF tb_intervalo
FOR VALUES FROM ('2019-07-01') TO ('2019-08-01');

CREATE TABLE tb_intervalo_2019_08


PARTITION OF tb_intervalo
FOR VALUES FROM ('2019-08-01') TO ('2019-09-01');

CREATE TABLE tb_intervalo_2019_09


PARTITION OF tb_intervalo
FOR VALUES FROM ('2019-09-01') TO ('2019-10-01');

CREATE TABLE tb_intervalo_2019_10


PARTITION OF tb_intervalo
FOR VALUES FROM ('2019-10-01') TO ('2019-11-01');

CREATE TABLE tb_intervalo_2019_11


PARTITION OF tb_intervalo
FOR VALUES FROM ('2019-11-01') TO ('2019-12-01');

CREATE TABLE tb_intervalo_2019_12


PARTITION OF tb_intervalo
FOR VALUES FROM ('2019-12-01') TO ('2020-01-01');

[>] E se tentássemos um valor fora do que foi previsto?

INSERT INTO tb_intervalo (dt) VALUES ('2020-01-01');

ERROR: no partition of relation "tb_intervalo" found for row


DETAIL: Partition key of the failing row contains (dt) = (2020-01-01).

Não foi definida uma partição padrão (DEFAULT). . .

[>] Criação de uma partição padrão:

CREATE TABLE tb_intervalo_default


PARTITION OF tb_intervalo DEFAULT;
321 13. Particionamento de tabelas

[>] Nova tentativa do INSERT que não funcionou:

INSERT INTO tb_intervalo (dt) VALUES ('2020-01-01');

OK! Sem problemas agora :)

[>] Vamos dar um TRUNCATE na tabela para iniciarmos os testes do zero:

TRUNCATE tb_intervalo RESTART IDENTITY;

[>] Utilizando o recurso de CTE, gerar uma massa de dados com um milhão de registros,
utilizando datas aleatórias com um intervalo entre 01/01/2019 a 31/03/2020:

WITH t (id_, random_date) AS (


SELECT
generate_series(1, 1000000),
'2019-01-01'::date +
(round(random() * ('2020-03-31'::date -
'2019-01-01'::date)))::int2
)
INSERT INTO tb_intervalo (dt)
SELECT random_date FROM t;

[>] Atualizar as estatísticas da tabela:

ANALYZE VERBOSE tb_intervalo;


322 13. Particionamento de tabelas

[>] Verificando o tamanho de cada partição e sua respectiva quantidade de registros:

SELECT
relname AS tabela,
pg_size_pretty(pg_relation_size(relname::regclass)) AS tamanho,
reltuples::int8 AS registros
FROM pg_class
WHERE relname ~ 'tb_intervalo'
AND relkind = 'r'
ORDER BY relname;

tabela | tamanho | registros


----------------------+---------+-----------
tb_intervalo_2019_01 | 2376 kB | 66985
tb_intervalo_2019_02 | 2192 kB | 61724
tb_intervalo_2019_03 | 2416 kB | 68096
tb_intervalo_2019_04 | 2320 kB | 65361
tb_intervalo_2019_05 | 2424 kB | 68269
tb_intervalo_2019_06 | 2344 kB | 66095
tb_intervalo_2019_07 | 2408 kB | 67813
tb_intervalo_2019_08 | 2416 kB | 68141
tb_intervalo_2019_09 | 2344 kB | 66049
tb_intervalo_2019_10 | 2408 kB | 67949
tb_intervalo_2019_11 | 2328 kB | 65634
tb_intervalo_2019_12 | 2432 kB | 68623
tb_intervalo_default | 7056 kB | 199261

Nota-se que, ao colocar uma parte da faixa de datas fora do intervalo. elas foram parar na
tabela default.

[>] Verificando o parâmetro de partition pruning:

SHOW enable_partition_pruning;

enable_partition_pruning
--------------------------
on
323 13. Particionamento de tabelas

[>] Executar uma consulta verificando seu plano execução com partition pruning habilitado:

EXPLAIN ANALYZE
SELECT count(*)
FROM tb_intervalo
WHERE dt BETWEEN '2019-07-01' AND '2019-07-27';

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Aggregate (cost=1460.84..1460.85 rows=1 width=8) (actual time=8.072..8.072 rows=1 loops=1)
-> Seq Scan on tb_intervalo_2019_07 tb_intervalo (cost=0.00..1313.84 rows=58800 width=0) (actual time=0.0
Filter: ((dt >= '2019-07-01'::date) AND (dt <= '2019-07-27'::date))
Rows Removed by Filter: 8642
Planning Time: 0.095 ms
Execution Time: 8.087 ms

Observa-se que a partir da condição de consulta, só foi necessário verificar dados em uma
única partição.

[>] Desabilitar partition pruning:

SET enable_partition_pruning = false;

[>] Executar uma consulta verificando seu plano execução com partition pruning desabilitado:

EXPLAIN ANALYZE
SELECT count(*)
FROM tb_intervalo
WHERE dt BETWEEN '2019-07-01' AND '2019-07-27';

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=14439.58..14439.59 rows=1 width=8) (actual time=26.967..29.905 rows=1 loops=1)
-> Gather (cost=14439.37..14439.58 rows=2 width=8) (actual time=26.838..29.900 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=13439.37..13439.38 rows=1 width=8) (actual time=24.758..24.762 rows=1 lo
-> Parallel Append (cost=0.00..13378.09 rows=24512 width=0) (actual time=19.380..23.679 rows=
-> Parallel Seq Scan on tb_intervalo_default tb_intervalo_13 (cost=0.00..2636.58 rows=1
Filter: ((dt >= '2019-07-01'::date) AND (dt <= '2019-07-27'::date))
Rows Removed by Filter: 198966
-> Parallel Seq Scan on tb_intervalo_2019_08 tb_intervalo_8 (cost=0.00..909.25 rows=1 w
Filter: ((dt >= '2019-07-01'::date) AND (dt <= '2019-07-27'::date))
Rows Removed by Filter: 68595
. . .
Planning Time: 0.553 ms
Execution Time: 29.989 ms

Nesse caso, pode ser notado que a execução demorou muito além de desnecessariamente ler
partições que não continham os dados que satisfazem a condição da consulta.
324 13. Particionamento de tabelas

[>] Habilitar partition pruning:

SET enable_partition_pruning = true;

[>] Desanexar uma partição:

ALTER TABLE tb_intervalo DETACH PARTITION tb_intervalo_2019_01;

Se novos dados pertinentes ao seu critério forem inseridos na tabela particionada, essa partição
não receberá mais os registros.

[>] Reanexar a partição:

ALTER TABLE tb_intervalo


ATTACH PARTITION tb_intervalo_2019_01
FOR VALUES FROM ('2019-01-01') TO ('2019-02-01');

Ao anexar uma partição, que pode ser qualquer tabela que tenha a mesma estrutura da tabela
particionada, precisamos determinar o critério de dados válidos da partição.

[>] E se realmente for necessário apagar uma partição?

DROP TABLE tb_intervalo_2019_02;

Um simples DROP TABLE resolve. É muito útil quando preciso liberar espaço em casos que não
há mais necessidade dos dados ali presentes.
Muito melhor para a base de dados do que dar um DELETE.

[>] Remover a tabela de teste:

DROP TABLE tb_intervalo;


325 13. Particionamento de tabelas

Particionamento por lista enumerada (LIST)


Particionamento baseado em lista de valores válidos.

[>] Tabela particionada por lista:

CREATE TABLE tb_cidade (


uf CHAR(2),
nome VARCHAR(50),
PRIMARY KEY (uf, nome)
) PARTITION BY LIST (uf);

[>] Partições:

-- Região Sul
CREATE TABLE tb_cidade_sul
PARTITION OF tb_cidade
FOR VALUES IN ('RS', 'SC', 'PR');

-- Região Sudeste
CREATE TABLE tb_cidade_sudeste
PARTITION OF tb_cidade
FOR VALUES IN ('SP', 'RJ', 'MG', 'ES');

-- Região Centro Oeste


CREATE TABLE tb_cidade_centro_oeste
PARTITION OF tb_cidade
FOR VALUES IN ('DF', 'GO', 'MS', 'MT');

-- Região Nordeste
CREATE TABLE tb_cidade_nordeste
PARTITION OF tb_cidade
FOR VALUES IN ('MA', 'PI', 'CE', 'RN', 'PB', 'PE', 'AL', 'SE', 'BA');

-- Região Norte
CREATE TABLE tb_cidade_norte
PARTITION OF tb_cidade
FOR VALUES IN ('AC', 'AM', 'RO', 'RR', 'AP', 'PA', 'TO');
326 13. Particionamento de tabelas

[>] Dados:

INSERT INTO tb_cidade (uf, nome) VALUES


('SP', 'São Paulo'),
('SP', 'Santo André'),
('SP', 'São Bernardo do Campo'),
('RJ', 'Niterói'),
('MG', 'Belo Horizonte'),
('MG', 'Varginha'),
('RN', 'Natal'),
('RO', 'Porto Velho'),
('RS', 'Porto Alegre'),
('PR', 'Curitiba');

[>] Selecionando os dados:

-- Todas cidades
SELECT uf, nome FROM tb_cidade;

uf | nome
----+-----------------------
RO | Porto Velho
RN | Natal
SP | São Paulo
SP | Santo André
SP | São Bernardo do Campo
RJ | Niterói
MG | Belo Horizonte
MG | Varginha
RS | Porto Alegre
PR | Curitiba

-- Cidades do Sul
SELECT uf, nome FROM tb_cidade_sul;

uf | nome
----+--------------
RS | Porto Alegre
PR | Curitiba
327 13. Particionamento de tabelas

-- Cidades do Sudeste
SELECT uf, nome FROM tb_cidade_sudeste;

uf | nome
----+-----------------------
SP | São Paulo
SP | Santo André
SP | São Bernardo do Campo
RJ | Niterói
MG | Belo Horizonte
MG | Varginha

[>] Para remover a tabela de teste:

DROP TABLE tb_cidade;


328 13. Particionamento de tabelas

Particionamento por hash


É o mais novo tipo de abordagem de particionamento de tabelas, que foi introduzido na versão
11 do PostgreSQL.
Seu comportamento se baseia em balancear a carga de dados entre as partições, a partir de
um campo em que passamos um módulo (MODULUS) e um resto (REMAINDER).

Para a parte prática, serão criadas 5 (cinco) partições. O módulo será 5 e para cada uma e
terá um resto variando de 0 (zero) a 4 (quatro).

[>] Criação da tabela particionada:

CREATE TABLE tb_hash (


id_ int PRIMARY KEY
GENERATED BY DEFAULT AS IDENTITY,
campo TEXT)
PARTITION BY HASH (id_);

[>] Criação das partições:

CREATE TABLE tb_hash_0


PARTITION OF tb_hash
FOR VALUES WITH (MODULUS 5, REMAINDER 0);

CREATE TABLE tb_hash_1


PARTITION OF tb_hash
FOR VALUES WITH (MODULUS 5, REMAINDER 1);

CREATE TABLE tb_hash_2


PARTITION OF tb_hash
FOR VALUES WITH (MODULUS 5, REMAINDER 2);

CREATE TABLE tb_hash_3


PARTITION OF tb_hash
FOR VALUES WITH (MODULUS 5, REMAINDER 3);

CREATE TABLE tb_hash_4


PARTITION OF tb_hash
FOR VALUES WITH (MODULUS 5, REMAINDER 4);
329 13. Particionamento de tabelas

[>] Dados:

INSERT INTO tb_hash (id_, campo)


SELECT
generate_series(1, 1000000), -- Um milhão de registros
sha512((random() * 1000)::text::bytea); -- Geração de texto aletório

[>] Atualize as estatísticas:

ANALYZE VERBOSE tb_hash;

[>] Tamanho e quantidade de registros das tabelas:

SELECT
relname AS tabela,
pg_size_pretty(pg_relation_size(relname::regclass)) AS tamanho,
reltuples::int8 AS registros
FROM pg_class
WHERE relname ~ 'tb_hash'
AND relkind = 'r'
ORDER BY relname;

tabela | tamanho | registros


-----------+---------+-----------
tb_hash_0 | 33 MB | 199134
tb_hash_1 | 33 MB | 200642
tb_hash_2 | 33 MB | 199715
tb_hash_3 | 33 MB | 200325
tb_hash_4 | 33 MB | 200184
330 13. Particionamento de tabelas

[>] Verificar a média de registros entre as partições:

WITH t AS (
SELECT
relname AS tabela,
pg_size_pretty(pg_relation_size(relname::regclass)) AS tamanho,
reltuples::int8 AS registros
FROM pg_class
WHERE relname ~ 'tb_hash_'
AND relkind = 'r'
ORDER BY relname)
SELECT round(avg(registros)) AS media FROM t;

media
--------
200000

[>] Remover a tabela de teste:

DROP TABLE tb_hash;


331 13. Particionamento de tabelas

——————– EXTRA ——————–

Particionamento multinível
Há situações em que é válido fazer particionamento aninhado de tabelas, um particionamento
multinível.
Cada nível de particionamento pode ser do mesmo tipo entre si ou não.

Neste case, as vendas foram divididas em estados e cada estado por cidade. Em ambos os
níveis, o tipo de particionamento utilizado é por lista, mas poderia ser misto.

Fig. 13.1: Particionamento multinível: de vendas geral para estado e de estado para cidade

[>] Criação da tabela particionada principal:

CREATE TABLE tb_venda(


id_ serial,
uf char(2),
cidade text,
dt date,
total int8,
PRIMARY KEY (id_, uf, cidade)
) PARTITION BY LIST (uf);
332 13. Particionamento de tabelas

[>] Criação de partições (e partições de partições):

-- SP ------------------------------------------------------------------------
CREATE TABLE tb_venda_sp
PARTITION OF tb_venda
FOR VALUES IN ('SP')
PARTITION BY LIST (cidade);

CREATE TABLE tb_venda_sp_sao_paulo


PARTITION OF tb_venda_sp
FOR VALUES IN ('São Paulo');

CREATE TABLE tb_venda_sp_santos


PARTITION OF tb_venda_sp
FOR VALUES IN ('Santos');

CREATE TABLE tb_venda_sp_bauru


PARTITION OF tb_venda_sp
FOR VALUES IN ('Bauru');

CREATE TABLE tb_venda_sp_santo_andre


PARTITION OF tb_venda_sp
FOR VALUES IN ('Santo André');

-- RJ ------------------------------------------------------------------------
CREATE TABLE tb_venda_rj
PARTITION OF tb_venda
FOR VALUES IN ('RJ')
PARTITION BY LIST (cidade);

CREATE TABLE tb_venda_rj_niteroi


PARTITION OF tb_venda_rj
FOR VALUES IN ('Niterói');

CREATE TABLE tb_venda_rj_rio_de_janeiro


PARTITION OF tb_venda_rj
FOR VALUES IN ('Rio de Janeiro');
333 13. Particionamento de tabelas

-- MG ------------------------------------------------------------------------
CREATE TABLE tb_venda_mg
PARTITION OF tb_venda
FOR VALUES IN ('MG')
PARTITION BY LIST (cidade);

CREATE TABLE tb_venda_mg_belo_horizonte


PARTITION OF tb_venda_mg
FOR VALUES IN ('Belo Horizonte');

CREATE TABLE tb_venda_mg_betim


PARTITION OF tb_venda_mg
FOR VALUES IN ('Betim');

[>] Listar tabelas criadas:

\dt

List of relations
Schema | Name | Type | Owner
--------+----------------------------+-------------------+----------
public | tb_venda | partitioned table | postgres
public | tb_venda_mg | partitioned table | postgres
public | tb_venda_mg_belo_horizonte | table | postgres
public | tb_venda_mg_betim | table | postgres
public | tb_venda_rj | partitioned table | postgres
public | tb_venda_rj_niteroi | table | postgres
public | tb_venda_rj_rio_de_janeiro | table | postgres
public | tb_venda_sp | partitioned table | postgres
public | tb_venda_sp_bauru | table | postgres
public | tb_venda_sp_santo_andre | table | postgres
public | tb_venda_sp_santos | table | postgres
public | tb_venda_sp_sao_paulo | table | postgres
334 13. Particionamento de tabelas

[>] Popular a tabela particionada principal:

COPY tb_venda (uf, cidade, dt, total) FROM STDIN DELIMITER ';';

Enter data to be copied followed by a newline.


End with a backslash and a period on a line by itself, or an EOF signal.

SP;São Paulo;2019-11-23;52157121
SP;São Paulo;2019-12-05;875441
SP;São Paulo;2020-07-08;71511524
SP;São Paulo;2020-09-01;257851
SP;São Paulo;2021-05-18;215478478
SP;São Paulo;2021-11-23;225485745
SP;Bauru;2020-08-23;545485
SP;Bauru;2021-10-05;897951
SP;Santos;2021-03-08;6981548
SP;Santos;2021-09-09;5487878
SP;Santo André;2021-04-19;215484
SP;Santo André;2021-07-13;21545
RJ;Rio de Janeiro;2020-04-20;187851
RJ;Rio de Janeiro;2021-06-19;105678475
RJ;Rio de Janeiro;2021-02-16;175485723
RJ;Niterói;2020-11-14;545985
RJ;Niterói;2021-12-31;1045477
MG;Belo Horizonte;2020-07-05;197008452
MG;Belo Horizonte;2020-08-03;197775703
MG;Belo Horizonte;2021-01-05;388391
MG;Belo Horizonte;2021-10-15;507922
MG;Betim;2020-11-11;3998000
MG;Betim;2021-06-27;4799014
MG;Betim;2021-07-17;159999

[>] Verificar o plano de execução de uma consulta na tabela:

EXPLAIN ANALYZE
SELECT count(*)
FROM tb_venda
WHERE cidade = 'Betim';

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Aggregate (cost=21.51..21.52 rows=1 width=8) (actual time=0.013..0.013 rows=1 loops=1)
-> Seq Scan on tb_venda_mg_betim tb_venda (cost=0.00..21.50 rows=5 width=0) (actual time=0.009..0.010 row
Filter: (cidade = 'Betim'::text)
Planning Time: 0.110 ms
Execution Time: 0.032 ms
14
Backup e Restauração
• Sobre Backup e Restauração
• Preparação do ambiente
• Backup Físico Off Line: Snapshot
• Backup Físico Online
• pg_basebackup
• Dump
• PITR - Point In Time Recovery

335
336 14. Backup e Restauração

Sobre Backup e Restauração


Como tudo que tem dados valiosos, bancos de dados PostgreSQL necessitam que sejam feitos
backups regularmente.
Embora os procedimentos sejam essencialmente simples, é importante ter um entendimento
claro das técnicas subjacentes e suas premissas.
O ato de fazer um backup implica uma cópia de segurança dos dados para caso haja algum
desastre capaz de causar a perda desses dados. Assim, eles podem ser recuperados via processo
de restauração de backup.
É bom lembrar que, tão importante quanto fazer / ter uma rotina de backup, é testar esses
backups feitos, ou seja, restaurar em outro lugar.
Para reduzir o espaço ocupado por um backup, é possível compactá-lo.

Backup físico

É o tipo de backup que é feito fazendo uma cópia de segurança dos arquivos de dados (data
files).
Costuma ser mais rápido que um dump, pois não há processamento dos dados, mas sim cópia
de arquivos.
Essa abordagem permite fazer o backup full e o backup incremental.

Backup full (completo)

Como o próprio nome sugere, é feita a cópia de segurança de todos os arquivos de dados da
base.

Backup incremental

Nesta estratégia de backup, é necessário que já haja um backup completo, pois apenas serão
adicionados os arquivos que foram modificados e / ou adicionados, relativos a esse backup
completo.

Backup lógico

Também conhecido como dump, é a transcrição lógica da base de dados para um arquivo ou
diretório (conforme o tipo de dump).
Um dump contém instruções SQL que devem ser processadas uma por uma, tanto no processo
de gerar o dump quanto em sua restauração.
Muito vantajoso para fazer migração para uma versão mais nova. No entanto, devido ao
processamento de cada instrução SQL, pode ser muito mais demorado do que um backup
físico.
Um dump pode ser de toda instância (com pg_dumpall), de apenas uma base de dados, de
um schema ou de uma tabela, ou seja, tem granulariadade.
337 14. Backup e Restauração

RPO - Recover Point Objective; Ponto Objetivo de Recuperação

É uma política de backup que diz respeito à tolerância de perda de dados que se pode ter em
caso de desastre.
RPO tem relação direta com a frequência com que são feitos os backups. Por exemplo, em
uma organização, o backup é feito diariamente. Então, o RPO é de 1 dia.
Quanto menor o RPO, melhor, pois em uma possível perda de dados o impacto será menor
também.

RTO - Recover Time Objective; Objetivo de Tempo de Recuperação

É a política de backup que trata do tempo máximo de restauração e reestabelecimento das


operações normais de um sistema.
Tempo máximo de parada após um incidente, o tempo máximo tolerado de indisponibilidade.
338 14. Backup e Restauração

Preparação do ambiente
Procedimentos para o laboratório de backup e restauração.

[$] Remover e criar novamente o banco de dados postgres:

dropdb --maintenance-db template1 postgres && \


createdb -T template0 --maintenance-db=template1 postgres

[$] Apagar qualquer base de dados que não seja postgres, template0 ou template1:

psql -Atqc \
"
SELECT datname
FROM pg_database
WHERE datname NOT IN ('template0', 'template1', 'postgres')
" | xargs -i dropdb {}

[$] Apagar qualquer tablespace que não seja padrão:

psql -Atqc \
"
SELECT spcname
FROM pg_tablespace
WHERE spcname NOT IN ('pg_default', 'pg_global');
" | xargs -i psql -qc "DROP TABLESPACE {};"

[#] Instalar o wget e limpar pacotes baixados:

apt install -y wget tree && apt clean

[$] Baixar o arquivo compactado do banco pagila em /tmp e, então, descompactá-lo:

wget https://ftp.postgresql.org/pub/projects/pgFoundry/dbsamples/pagila/\
pagila/pagila-0.10.1.zip -P /tmp/ && \
cd /tmp && \
unzip pagila-0.10.1.zip
339 14. Backup e Restauração

[$] Criar uma base de dados vazia:

createdb pagila

[$] Respectivamente, importar os arquivos de estrutura e de dados:

psql -f pagila-0.10.1/pagila-schema.sql pagila && \


psql -f pagila-0.10.1/pagila-data.sql pagila

Diretórios

A seguir, a descrição de diretórios que serão utilizados para os exercícios de backup conforme
a finalidade.

Backup físico

Descrição Diretório
Backup de dados /var/backups/pgsql/<versão majoritária>/data
Backup de WAL /var/backups/pgsql/<versão majoritária>/wal

Backup lógico (dump)

Descrição Diretório
Compacto /var/backups/pgsql/<versão majoritária>/dump/compact
Custom /var/backups/pgsql/<versão majoritária>/dump/custom
Diretório /var/backups/pgsql/<versão majoritária>/dump/dir
Tar /var/backups/pgsql/<versão majoritária>/dump/tar
Texto plano /var/backups/pgsql/<versão majoritária>/dump/text

[#] Variável de ambiente para versão majoritária do PostgreSQL:

read -p 'Digite a versão majoritária: ' PGMAJOR


340 14. Backup e Restauração

[#] Criação de diretórios de backup:

# Variável para diretório principal de backups da versão majoritária


export BKPDIR="/var/backups/pgsql/${PGMAJOR}"
# Diretórios de backups físicos
mkdir -pm 700 ${BKPDIR}/{data,wal}
# Diretórios de dumps
mkdir -pm 700 ${BKPDIR}/dump/{compact,custom,dir,tar,text}

[#] Alterar a propriedade para o usuário e grupo postgres:

chown -R postgres: /var/backups/pgsql

[#] Verificar a árvore de diretórios de backup:

tree /var/backups/pgsql

/var/backups/pgsql
|-- 13
|-- data
|-- dump
| |-- compact
| |-- custom
| |-- dir
| |-- tar
| |-- text
|-- wal
9 directories, 0 files
341 14. Backup e Restauração

Backup físico off-line: snapshot


Para esta estratégia de backup, é necessário que o cluster esteja inativo.
É feita, então, uma cópia do diretório de dados ($PGDATA).

[$] Parando o cluster de trabalho:

pg_ctl stop

[$] Acessar o diretório pai de PGDATA:

cd `dirname "${PGDATA}"`

[$] A partir do diretório pai, criar um tar.gz em /tmp/ do diretório do cluster (data):

tar cvzf /tmp/cluster.tar.gz `basename ${PGDATA}`

[$] Descompactar o arquivo em /tmp:

tar xvf /tmp/cluster.tar.gz -C /tmp/

[$] A partir da descompactação, criou-se o diretório data em /tmp.


Será preciso editar seu postgresql.conf e desativar alguns parâmetros de configuração:

vim /tmp/data/postgresql.conf

Desativar quem têm vínculos com o diretório de dados do cluster principal, por isso, precisam
ser comentados:

• data_directory
• hba_file
• ident_file
• external_pid_file
342 14. Backup e Restauração

[$] Inicializando o cluster de teste:

pg_ctl -D /tmp/data start

[$] Listagem dos bancos do cluster:

psql -Atqc 'SELECT datname FROM pg_database;'

template1
template0
postgres
pagila

[$] Parando o cluster de teste:

pg_ctl -D /tmp/data/ stop

[$] Inicializando o cluster de trabalho:

pg_ctl start
343 14. Backup e Restauração

Backup Físico Online


No backup físico online, os arquivos de dados da base são copiados com o serviço do banco
rodando.
É preciso que tenha um arquivamento contínuo dos logs de transação (WAL).

Arquivamento contínuo

Logs de transação (WAL) possibilitam usar uma terceira estratégia para fazer backup de bases
de dados: podemos combinar backup em nível de sistema de arquivos com o backup de
arquivos do WAL.
Se precisar fazer uma recuperação, restaura-se o backup do sistema de arquivos e, então,
reaplica-se os arquivos do WAL que foram guardados em backup (arquivados) para trazer o
sistema ao estado atual. O arquivamento contínuo é feito a partir do arquivamento do WAL
(arquivos de log de transação), em que cada WAL arquivado pode ser utilizado para replay
(reaplicação).
Essa abordagem é mais complexa para administrar do que cada uma das outras abordagens,
mas tem alguns benefícios significantes:

• Dispensa backup de sistema de arquivos consistente como ponto de partida. Qualquer


inconsistência interna no backup será corrigida pela reaplicação do log de transação
(isso não é tão diferente do que acontece durante uma recuperação de falha). Não é
necessário um sistema de arquivos com a capacidade de fazer snapshots, apenas o tar
ou uma simples ferramenta de arquivamento similar.

• Não precisa fazer o replay de entradas do WAL de todo caminho até o fim. Poderia parar
o replay a qualquer ponto e ter um snapshot consistente da base de dados como era
antes. Assim, essa técnica suporta recuperação em um ponto no tempo (point-in-time
recovery ). É possível restaurar a base de dados para seu estado em qualquer tempo
desde que seu backup foi feito.

• Se continuamente alimentarmos as entradas de arquivos de WAL para outra máquina


que tem sido carregada com o mesmo arquivo de backup base, tem-se um sistema de
warm standby : a qualquer ponto pode-se levantar a segunda máquina e vai ter uma
cópia quase atual da base de dados.

• Estratégico para grandes bases de dados, onde não é conveniente fazer backups inteiros
frequentemente.

Para iniciar o servidor em modo de recuperação de backup (recovery mode), é preciso criar
um arquivo chamado recovery.signal dentro do diretório de dados - apenas um arquivo vazio
344 14. Backup e Restauração

que serve como gatilho.

Configuração de arquivamento contínuo

Até a versão 11 do PostgreSQL, para fins de restauração de backup online ou PITR, havia
um arquivo dentro do $PGDATA denominado recovery.conf.
Era um arquivo de configuração com as mesmas características do postgresql.conf, mas para
fins de restauração.
A partir do PostgreSQL 12 o recovery.conf foi abolido, tendo seus parâmetros absorvidos pelo
próprio postgresql.conf.
Suas configurações só se aplicam durante a recuperação.
Não podem ser alteradas uma vez que a operação de restauração se iniciou.

Variáveis especiais de restauração

• %f:
apenas o nome do arquivo WAL;
• %p: caminho completo do arquivo WAL;
• %r: nome do arquivo que contém o último ponto de reinício válido, o qual pode ser
tomado como base para remover seus antecessores e economizar espaço;
• %%: escreve o caractere %.

Funções de controle de recuperação (recovery)

• pg_wal_replay_pause
Pausa uma recuperação.
Enquanto uma recuperação estiver pausada, nenhuma alteração será aplicada.
Se for um servidor standby, todas novas consultas feitas enxergarão o mesmo snapshot
consistente do cluster.
Restrita a super usuários.

• pg_wal_replay_resume
Retoma uma recuperação que foi pausada.

• pg_is_wal_replay_paused
Retorna true se uma recuperação estiver pausada.

• pg_promote
Promove um standby para primário.

https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-RECOVERY-
345 14. Backup e Restauração

[$] Nome, configuração e contexto dos parâmetros:

psql -qc "


SELECT
name, setting, context
FROM pg_settings
WHERE name IN (
'wal_level',
'archive_mode',
'archive_command',
'restore_command',
'archive_cleanup_command');
"

name | setting | context


-------------------------+------------+------------
archive_cleanup_command | | sighup
archive_command | (disabled) | sighup
archive_mode | off | postmaster
restore_command | | postmaster
wal_level | replica | postmaster

Três parâmetros precisam que o serviço seja reinicializado para serem aplicados.

[$] Variável de ambiente para versão majoritária do PostgreSQL:

read -p 'Digite a versão majoritária: ' PGMAJOR

[$] Edição do arquivo de configuração:

vim ${PGDATA}/postgresql.conf

wal_level = replica
archive_mode = on
archive_command = 'rsync -a %p /var/backups/pgsql/<versão majoritária>/wal/%f'
archive_cleanup_command = 'pg_archivecleanup /var/backups/pgsql/<versão majoritária>/wal %r
'
restore_command = 'rsync -a /var/backups/pgsql/<versão majoritária>/wal/%f %p'
346 14. Backup e Restauração

Aviso

Não há garantia de gravação no destino com os comandos cp, rsync e scp.


Para uso em produção, utilize algum que dê essa garantia, tais como barman-wal-archive e
pgbackrest archive-push.
É altamente recomendável também que, em produção, o arquivamento do WAL seja enviado
para outra máquina.

[$] Reinicialização do cluster para aplicar as novas configurações:

pg_ctl restart

Backup de arquivamento contínuo

[$] Variável de ambiente para identificar o momento atual:

NOW=`date +%Y%m%d-%H%M`

[$] Verificando o valor da variável:

echo ${NOW}

20210308-1516

[$] Função pg_start_backup que avisa o servidor que será feito um backup:

CHKPNT_START=`psql -Atqc "SELECT pg_start_backup('${NOW}');"`

É criada a variável de ambiente CHKPNT_START, cujo valor é a saída do comando.


347 14. Backup e Restauração

[$] Localização inicial do log de transações do backup:

echo ${CHKPNT_START}

0/13000028

[$] Verificando o conteúdo do arquivo de etiqueta de backup:

cat ${PGDATA}/backup_label

START WAL LOCATION: 0/13000028 (file 000000030000000000000013)


CHECKPOINT LOCATION: 0/13000060
BACKUP METHOD: pg_start_backup
BACKUP FROM: master
START TIME: 2021-03-08 15:17:22 -03
LABEL: 20210308-1516
START TIMELINE: 3

[$] Converte a localização de backup em nome do arquivo de segmento do WAL:

psql -qc "SELECT pg_walfile_name('${CHKPNT_START}');"

pg_walfile_name
--------------------------
000000020000000000000013

[$] Cria um backup compactado do diretório PGDATA:

tar czf /var/backups/pgsql/${PGMAJOR}/data/cluster.tar.gz ${PGDATA}

[$] Avisa o servidor que o backup foi concluído:

CHKPNT_FINAL=`psql -Atqc "SELECT pg_stop_backup();"`

[$] Localização final do log de transações do backup:

echo ${CHKPNT_FINAL}
348 14. Backup e Restauração

0/13000138

[$] Converte a localização de backup em nome do arquivo de segmento do WAL:

psql -qc "SELECT pg_walfile_name('${CHKPNT_FINAL}');"

pg_walfile_name
--------------------------
000000020000000000000013

[$] Parando o cluster de trabalho e, em seguida, remover o diretório de dados:

pg_ctl stop && rm -fr ${PGDATA}

[$] Restauração de backup:

tar xvf /var/backups/pgsql/${PGMAJOR}/data/cluster.tar.gz -C /

[$] Cria o arquivo de sinal para avisar o PostgreSQL da restauração:

touch ${PGDATA}/recovery.signal

[$] Iniciar o serviço do PostgreSQL:

pg_ctl start

[$] Listando os bancos do cluster:

psql -Atqc 'SELECT datname FROM pg_database;'

postgres
pagila
template1
template0
349 14. Backup e Restauração

pg_basebackup
O pg_basebackup é um utilitário para fazer backups online de clusters PostgreSQL. Os back-
ups são feitos sem afetar outros clientes na base e podem ser usados para PITR (Point In
Time Recovery ) e / ou como ponto de partida para log shipping ou servidores standby de
replicação via streaming.

Case
Será feito o backup físico online de uma instância que tem um tablespace.

[$] Variável de ambiente para versão majoritária do PostgreSQL:

read -p 'Digite a versão majoritária: ' PGMAJOR

[$] Criação de diretório de tablespace:

mkdir -pm 0700 /var/local/pgsql/${PGMAJOR}/ts/alpha

[$] Criação de um tablespace na instância principal:

psql -qc "CREATE TABLESPACE ts_alpha \


LOCATION '/var/local/pgsql/${PGMAJOR}/ts/alpha';"

[$] Criação de uma base de dados:

psql -qc 'CREATE DATABASE db_teste TABLESPACE = ts_alpha;'

[$] Backup com pg_basebackup:

pg_basebackup \
-X stream \
-T /var/local/pgsql/${PGMAJOR}/ts/alpha=/var/backups/pgsql/${PGMAJOR}/ts/alpha \
-D /var/backups/pgsql/${PGMAJOR}/data/pg_basebackup
350 14. Backup e Restauração

-X: método de inclusão de wal, que aqui foi via streaming


-T: mapeamento de tablespace; origem=destino
-D: diretório de destino do backup

[$] Alterar a porta padrão para 5433 via sed:

sed -i 's/^port = 5432/port = 5433/g' \


/var/backups/pgsql/${PGMAJOR}/data/pg_basebackup/postgresql.conf

sed -i 's/^#port = 5432/port = 5433/g' \


/var/backups/pgsql/${PGMAJOR}/data/pg_basebackup/postgresql.conf

[$] Alterar o postgresql.conf e comentar os parâmetros:

# data_directory
sed 's/^\(data_directory.*\)/#\1/g' -i \
/var/backups/pgsql/${PGMAJOR}/data/pg_basebackup/postgresql.conf

# hba_file
sed 's/^\(hba_file.*\)/#\1/g' -i \
/var/backups/pgsql/${PGMAJOR}/data/pg_basebackup/postgresql.conf

# ident_file
sed 's/^\(ident_file.*\)/#\1/g' -i \
/var/backups/pgsql/${PGMAJOR}/data/pg_basebackup/postgresql.conf

# external_pid_file
sed 's/^\(external_pid_file.*\)/#\1/g' -i \
/var/backups/pgsql/${PGMAJOR}/data/pg_basebackup/postgresql.conf

[$] Subir a instância:

pg_ctl start -D /var/backups/pgsql/${PGMAJOR}/data/pg_basebackup

[$] Exibir tablespaces:

psql -p 5433 -qc '\db'

List of tablespaces
Name | Owner | Location
------------+----------+--------------------------------
pg_default | postgres |
pg_global | postgres |
ts_alpha | postgres | /var/backups/pgsql/13/ts/alpha
351 14. Backup e Restauração

[$] Exibir bases de dados:

psql -p 5433 -Atqc 'SELECT datname FROM pg_database'

postgres
pagila
template1
template0
db_teste

[$] Parar a instância:

pg_ctl -D /var/backups/pgsql/${PGMAJOR}/data/pg_basebackup stop


352 14. Backup e Restauração

Dump
A ideia por trás deste método é gerar um arquivo com comandos SQL que, ao ser utilizado
para uma restauração do servidor, recriará o banco de dados ou o cluster no mesmo estado
como estava na hora do dump.
É também conhecido como backup lógico.
Não permite a estratégia de backup incremental.
Não permite recuperar dados em uma parte do tempo (PITR: Point In Time Recovery ).
Pode ser usado como método de migração entre uma versão e outra do PostgreSQL; faz-se o
dump na versão antiga e restaura na nova.

pg_dump: dump de bases de dados individuais

O pg_dump é um utilitário para fazer dump de uma base de dados do PostgreSQL.


Faz backups consistentes mesmo se a base de dados estiver sendo usada concorrentemente,
como também não bloqueia outros usuários a acessarem a base (leitura ou escrita).
Os dumps podem ser feitos em formato de texto plano; que pode ser restaurado pelo utilitário
psql, ou em formato binário; restaurado pelo utilitário pg_restore:

• Texto plano: psql;


• Binário: pg_restore;

A partir da versão 9.3 do PostgreSQL, o pg_dump conta com uma opção muito interessante:
-j njobs ou --jobs=n jobs

Tal opção permite o dump rodar em paralelo, dividindo em N trabalhos (jobs) simultanea-
mente.
Essa opção reduz o tempo em que o dump é feito, mas também aumenta a carga no servidor
de banco de dados.
Só pode ser usada com o formato de saída de diretório (-Fd), porque é o único formato em
que múltiplos processos podem escrever seus dados ao mesmo tempo.
O pg_dump abre N jobs + 1 conexões, o que depende da configuração de max_connections ser
alta o suficiente para acomodar todas.
353 14. Backup e Restauração

pg_restore

O pg_restore é um utilitário para restaurar uma base PostgreSQL de um arquivo criado pelo
pg_dump. Ele emitirá comandos necessários para reconstruir a base ao estado como era no
momento que o arquivo de dump foi salvo.
Os arquivos de dump são projetados para serem portáveis entre arquiteturas.
Dentre seus parâmetros de configuração, é interessante comentar sobre:

-j n jobs ou --jobs=n jobs

Assim como o pg_dump divide o trabalhomutilizando multi-processamento, a restauração


será dividida em N jobs (trabalhos), sendo que cada job é um processo ou uma thread,
dependendo do sistema operacional, e usa uma conexão separada para o servidor. É aceito
apenas no formato custom (-Fc).

Dividindo a tarefa em vários jobs, é reduzido o tempo, porém, exige mais do servidor.

[$] Listando 5 (cinco) tabelas do banco de dados pagila:

psql -d pagila -qc "


SELECT
relname tabela
FROM pg_stat_user_tables
ORDER by tabela LIMIT 5;
"

tabela
----------
actor
address
category
city
country

[$] Variável de ambiente para versão majoritária do PostgreSQL:

read -p 'Digite a versão majoritária: ' PGMAJOR

[$] Dump formato diretório em 7 jobs:

pg_dump -j7 -Fd pagila -f /var/backups/pgsql/${PGMAJOR}/dump/dir


354 14. Backup e Restauração

[$] Apagando o banco de dados pagila e criando um novo vazio:

dropdb pagila && createdb pagila

[$] Fazendo o restore do formato diretório em 7 jobs:

pg_restore -d pagila -j7 -Fd /var/backups/pgsql/${PGMAJOR}/dump/dir

[$] Listando 5 (cinco) tabelas do banco de dados pagila:

psql -d pagila -qc "


SELECT
relname tabela
FROM pg_stat_user_tables
ORDER by tabela LIMIT 5;
"

tabela
----------
actor
address
category
city
country

[$] Dump formato tar:

pg_dump -Ft pagila > /var/backups/pgsql/${PGMAJOR}/dump/tar/pagila.tar

[$] Apagando o banco de dados pagila e criando um novo vazio:

dropdb pagila && createdb pagila

[$] Restore do formato tar:

pg_restore -d pagila -Ft /var/backups/pgsql/${PGMAJOR}/dump/tar/pagila.tar


355 14. Backup e Restauração

[$] Listando 5 (cinco) tabelas do banco de dados pagila:

psql -d pagila -qc "


SELECT
relname tabela
FROM pg_stat_user_tables
ORDER by tabela LIMIT 5;
"

tabela
----------
actor
address
category
city
country

[$] Dump formato custom:

pg_dump -Fc pagila > /var/backups/pgsql/${PGMAJOR}/dump/custom/pagila.dump

[$] Apagando o banco de dados pagila e criando um novo vazio:

dropdb pagila && createdb pagila

[$] Restore do formato custom:

pg_restore -j5 -d pagila \


-Fc /var/backups/pgsql/${PGMAJOR}/dump/custom/pagila.dump
356 14. Backup e Restauração

[$] Listando 5 (cinco) tabelas do banco de dados pagila:

psql -d pagila -qc "


SELECT
relname tabela
FROM pg_stat_user_tables
ORDER by tabela LIMIT 5;
"

tabela
----------
actor
address
category
city
country

[$] Dump formato texto com instruções de criação da base de dados:

pg_dump -C pagila > /var/backups/pgsql/${PGMAJOR}/dump/text/pagila.sql

[$] Apagando o banco de dados pagila:

dropdb pagila

[$] Restaurando o banco de dados pagila pelo arquivo texto:

psql -f /var/backups/pgsql/${PGMAJOR}/dump/text/pagila.sql

[$] Listando 5 (cinco) tabelas do banco de dados pagila:

psql -d pagila -qc "


SELECT
relname tabela
FROM pg_stat_user_tables
ORDER by tabela LIMIT 5;
"

tabela
----------
actor
address
357 14. Backup e Restauração

category
city
country

[$] Dump com criação da base e compactado em formato gzip:

pg_dump -C pagila | \
gzip -9 > /var/backups/pgsql/${PGMAJOR}/dump/compact/pagila.gz

[$] Apagando o banco de dados pagila:

dropdb pagila

[$] Restaurando o banco a partir de um arquivo gzip:

gunzip -c /var/backups/pgsql/${PGMAJOR}/dump/compact/pagila.gz | psql

[$] Listando 5 (cinco) tabelas do banco de dados pagila:

psql -d pagila -qc "


SELECT
relname tabela
FROM pg_stat_user_tables
ORDER by tabela LIMIT 5;
"

tabela
----------
actor
address
category
city
country

[$] Dump com criação da base e compactado em formato bzip2:

pg_dump -C pagila | \
bzip2 -9 > /var/backups/pgsql/${PGMAJOR}/dump/compact/pagila.bz2

[$] Apagando o banco de dados pagila:


358 14. Backup e Restauração

dropdb pagila
359 14. Backup e Restauração

[$] Restaurando o banco a partir de um arquivo bzip2:

bunzip2 -c /var/backups/pgsql/${PGMAJOR}/dump/compact/pagila.bz2 | psql

[$] Listando 5 (cinco) tabelas do banco de dados pagila:

psql -d pagila -qc "


SELECT
relname tabela
FROM pg_stat_user_tables
ORDER by tabela LIMIT 5;
"

tabela
----------
actor
address
category
city
country

[$] Listagem dos dumps e seus tamanhos:

du -hs /var/backups/pgsql/${PGMAJOR}/dump/* | \
fgrep -v compact && \
du -hs /var/backups/pgsql/${PGMAJOR}/dump/compact/*

696K /var/backups/pgsql/13/dump/custom
748K /var/backups/pgsql/13/dump/dir
2.9M /var/backups/pgsql/13/dump/tar
2.8M /var/backups/pgsql/13/dump/text
476K /var/backups/pgsql/13/dump/compact/pagila.bz2
612K /var/backups/pgsql/13/dump/compact/pagila.gz

No final das contas, vemos que podemos reduzir o tempo do dump, do tamanho do arquivo
ou diretório resultante conforme o tipo de dump utilizado.

pg_dumpall

O pg_dumpall é um utilitário que extrai um cluster de banco de dados para um script.


Seu modus operandi é similar ao pg_dump, mas o pg_dumpall somente tem o formato de
texto plano e faz dump de todo o cluster e não somente de uma base de dados.
O pg_dumpall também é muito utilizado para fazer migrações, seja da mesma versão do
PostgreSQL seja entre versões diferentes.
360 14. Backup e Restauração

[$] Criação de outro cluster:

initdb -U postgres -D /tmp/cluster2

[$] Modificando a porta de escuta de serviço no postgresql.conf para 5433:

sed -i 's/^#port = 5432/port = 5433/g' /tmp/cluster2/postgresql.conf

[$] Inicialização do cluster:

pg_ctl -D /tmp/cluster2 start

[$] Listando as bases de dados do novo cluster:

psql -p 5433 -U postgres -Atqc 'SELECT datname FROM pg_database;'

postgres
template1
template0

[$] Dump direto de um cluster para outro (velho para novo):

pg_dumpall | psql -p 5433 -U postgres

[$] Listando novamente as bases de dados do novo cluster:

psql -p 5433 -U postgres -Atqc 'SELECT datname FROM pg_database;'

postgres
pagila
template1
template0
361 14. Backup e Restauração

[$] Listar 5 (cinco) tabelas do banco de dados pagila no novo cluster:

psql -d pagila -p 5433 -U postgres -Atqc \


'SELECT relname FROM pg_stat_user_tables ORDER by relname LIMIT 5'

actor
address
category
city
country

[$] Redirecionando a saída do dump para um arquivo:

pg_dumpall > /tmp/cluster.dump.sql

[$] Redirecionando a saída do dump para o compactador bzip2 que cria um arquivo com-
pactado:

pg_dumpall | bzip2 -9 -c > /tmp/cluster.dump.sql.bz2

[$] Listando os dois arquivos criados e seus respectivos tamanhos:

ls -lh /tmp/cluster.* | awk '{print $(NF) " => " $5}'

/tmp/cluster.dump.sql => 2.8M


/tmp/cluster.dump.sql.bz2 => 476K

[$] Parando e depois excluindo o novo cluster:

pg_ctl -D /tmp/cluster2 -m immediate stop && rm -fr /tmp/cluster2

[$] Criação de outro cluster:

initdb -U postgres -D /tmp/cluster2


362 14. Backup e Restauração

[$] Modificando a porta de escuta de serviço no postgresql.conf para 5433:

sed -i 's/^#port = 5432/port = 5433/g' /tmp/cluster2/postgresql.conf

[$] Inicialização do cluster:

pg_ctl -D /tmp/cluster2 start

[$] Cronometrando a importação de um arquivo de dump de texto puro:

time psql -p 5433 -f /tmp/cluster.dump.sql

. . .
real 0m1.753s
user 0m0.021s
sys 0m0.009s

[$] Parando e depois excluindo o novo cluster:

pg_ctl -D /tmp/cluster2 -m immediate stop && rm -fr /tmp/cluster2

[$] Criação de outro cluster:

initdb -U postgres -D /tmp/cluster2

[$] Modificando a porta de escuta de serviço no postgresql.conf para 5433:

sed -i 's/^#port = 5432/port = 5433/g' /tmp/cluster2/postgresql.conf

[$] Inicialização do cluster:

pg_ctl -D /tmp/cluster2 start


363 14. Backup e Restauração

[$] Cronometrando a importação de um arquivo de dump compactado:

time bunzip2 -dc /tmp/cluster.dump.sql.bz2 | psql -p 5433

. . .
real 0m1.841s
user 0m0.109s
sys 0m0.018s

Ao utilizar compactação, economizará espaço, mas, se for preciso fazer uma restauração a
partir de um dump compactado, levará mais tempo.

[$] Parando o cluster:

pg_ctl -D /tmp/cluster2/ stop


364 14. Backup e Restauração

PITR - Point In Time Recovery


Em português, significa “Recuperar em um Ponto no Tempo”.
É um recurso extremamente interessante para não somente poder recuperar uma base de
dados, mas também ter a facilidade de recuperar a base em um determinado momento.
Esse determinado momento é a partir de quando fazemos um backup da base.
Tudo o que for feito depois desse backup pode ser recuperado como se fosse uma máquina do
tempo.
Ou seja, se o ponto que quer recuperar é depois do backup, é possível fazê-lo!

Backup
|
Antes do backup | Pós Backup
|
Linha do tempo: -------------------o------------------------------------->

Como demonstrado acima, conforme a linha do tempo, somente a partir do momento em que
se faz um backup é possível utilizar o PITR.

[$] Executar o backup com pg_basebackup com checkpoint imediato:

pg_basebackup -c fast -X stream -D /var/backups/pgsql/${PGMAJOR}/data/pitr

[$] Pós backup inserir dados na base:

psql -d pagila -qc "


INSERT INTO actor (first_name,last_name) VALUES
('Roberto','Vivar'),
('Ramón','Bolaños'),
('Florinda','de las Nieves'),
('Carlos','Valdez'),
('María Antonieta','Meza'),
('Edgar','Villagrán');
"

[$] Variável que guarda um ponto no tempo em que a base de dados está OK:

STATE_0=`date "+%Y-%m-%d %H:%M:%S %Z"`


365 14. Backup e Restauração

[$] Um pouco de espera. . . :

sleep 7

[$] Desastre!!! Dados apagados por engano!:

psql -d pagila -qc 'DELETE FROM actor WHERE actor_id > 200 RETURNING *;'

actor_id | first_name | last_name | last_update


----------+-----------------+---------------+----------------------------
201 | Roberto | Vivar | 2021-03-08 18:59:49.825964
202 | Ramón | Bolaños | 2021-03-08 18:59:49.825964
203 | Florinda | de las Nieves | 2021-03-08 18:59:49.825964
204 | Carlos | Valdez | 2021-03-08 18:59:49.825964
205 | María Antonieta | Meza | 2021-03-08 18:59:49.825964
206 | Edgar | Villagrán | 2021-03-08 18:59:49.825964

[$] Verificando a perda:

psql -d pagila -Atqc \


"
SELECT
first_name||' '||last_name actor
FROM actor
WHERE actor_id > 200;
"

Nada retornado; realmente houve perda dos registros. . .

[$] Exibindo nome, configuração e contexto dos parâmetros:

psql -qc "SELECT name, setting, context FROM pg_settings


WHERE name IN ('wal_level',
'archive_mode',
'archive_command',
'restore_command',
'archive_cleanup_command');
"

name | setting | context


-------------------------+------------------------------------------------+------------
archive_cleanup_command | pg_archivecleanup /var/backups/pgsql/13/wal %r | sighup
archive_command | rsync -a %p /var/backups/pgsql/13/wal/%f | sighup
366 14. Backup e Restauração

archive_mode | on | postmaster
restore_command | rsync -a /var/backups/pgsql/13/wal/%f %p | postmaster
wal_level | replica | postmaster

[$] Parar o cluster:

pg_ctl stop

[$] Editar o postgresql.conf; recovery_target_time = ${STATE_0}:

sed "s|\(^#recovery_target_time = .*\)|\1\nrecovery_target_time = '${STATE_0}'|g" \


-i ${PGDATA}/postgresql.conf

[$] Copiar o postgresql.conf para backup:

cp ${PGDATA}/postgresql.conf \
/var/backups/pgsql/${PGMAJOR}/data/pitr/postgresql.conf

[$] Cria o arquivo gatilho para restauração ao iniciar:

touch /var/backups/pgsql/${PGMAJOR}/data/pitr/recovery.signal

[$] Inicializa o cluster de backup:

pg_ctl -D /var/backups/pgsql/${PGMAJOR}/data/pitr start

[$] Verificando se o backup está OK:

psql -d pagila -Atqc \


"SELECT first_name||' '||last_name actor FROM actor \
WHERE actor_id > 200;"

Roberto Vivar
Ramón Bolaños
Florinda de las Nieves
Carlos Valdez
367 14. Backup e Restauração

María Antonieta Meza


Edgar Villagrán

[$] Para o cluster de backup:

pg_ctl -D /var/backups/pgsql/${PGMAJOR}/data/pitr stop

[$] Atualiza o PGDATA com os arquivos do backup:

rsync -av /var/backups/pgsql/${PGMAJOR}/data/pitr/* ${PGDATA}/

[$] Inicia o PostgreSQL do cluster principal:

pg_ctl start

[$] Verificando se a instância principal está OK:

psql -d pagila -Atqc \


"SELECT first_name||' '||last_name actor FROM actor \
WHERE actor_id > 200;"

Roberto Vivar
Ramón Bolaños
Florinda de las Nieves
Carlos Valdez
María Antonieta Meza
Edgar Villagrán

[$] Verifica se o PostgreSQL está em modo recovery:

psql -Atqc 'SELECT pg_is_in_recovery()'

Resposta afirmativa; “t” de true.


Isso significa que ele está operando como um standby, ou seja, operações de leitura apenas.

[$] Promove a instância, permitindo assim leitura e escrita:


368 14. Backup e Restauração

pg_ctl promote
369 14. Backup e Restauração

[$] Verifica se o PostgreSQL está em modo recovery:

psql -Atqc 'SELECT pg_is_in_recovery()'

Agora o resultado é “f” de false.


Saiu do modo recovery e agora aceita também operações de escrita.
15
Replicação
• O que é replicação
• Replicação física via streaming
• Replicação lógica
• Soluções de terceiros para alta disponibilidade no PostgreSQL
• Gerenciamento de cluster de replicação e alta disponibilidade com repmgr

370
371 15. Replicação

O que é replicação
É o processo de copiar dados de um servidor a outro de forma contínua entre eles.

Conceitos de replicação

• Upstream
É o servidor de origem, onde outro(s) servidor(es) busca(m) seus dados.

• Primário (primary) / mestre (master)


É o servidor upstream, não tem nenhum outro upstream acima dele.

• Secundário (secondary) / standby / réplica


Tipo de servidor dentro de um cluster de replicação que recebe dados de um upstream.

• Replicação cascateada
Quando um standby é standby de outro standby e não de um servidor primário. Ou
seja, quando um standby tem como upstream outro standby.

Fig. 15.1: Papéis de servidores em um cluster de replicação


372 15. Replicação

• Replicação física
Envia alterações de blocos byte a byte do WAL.

• Replicação lógica
É um método em que são replicados objetos e suas alterações, linha por linha.
Permite um controle granular tanto da replicação de dados quanto da segurança.

• Replicação síncrona
Confirma se as alterações feitas em uma transação foram devidamente replicadas para
os servidores standbys. Isso estende o nível padrão de durabilidade que é oferecido por
uma efetivação.
Com replicação síncrona faz com que tenha muito mais demora nas efetivações.

• Replicação assíncrona
Mesmo que irrisório há um tempo de propagação entre primário e standbys.
É uma abordagem mais rápida que a replicação síncrona, durante algum tempo, mesmo
que pouquíssimo tempo, os standbys estarão defasados com relação ao primário.
Se o servidor primário sofre parada abrupta (crash), certas transações que foram efe-
tivadas podem não ter sido replicadas para os standbys, causando perda de dados. A
quantidade de perda de dados é proporcional ao atraso da replicação no momento do
failover.

• Failover
Situação em que o servidor primário está fora e um dos standbys assume como primário
para fins de alta disponibilidade.

• Failback ou rejoin
Processo de reintegrar ao cluster de replicação um servidor que falhou.
373 15. Replicação

Slots de replicação

Fornecem uma maneira automatizada para garantir que o servidor master não removerá ar-
quivos do WAL até que eles tenham sido recebidos por todos standbys e que o master não
remova linhas que poderiam causar um conflito de recuperação quando o standby estiver fora.
É possível prevenir a remoção de segmentos do WAL utilizando o parâmetro wal_keep_size em
vez de slots de replicação. Ou também armazenando os segmentos usando o archive_command.
No entanto, esses métodos alternativos aos slots de replicação frequentemente resultam em
reter mais segmentos de WAL do que realmente precisa, enquanto que os slots de replicação
mantêm apenas o que for necessário.
É importante saber que slots de replicação podem reter muitos segmentos de WAL que podem
encher o espaço alocado para o pg_wal (diretório de armazenamento padrão dos segmentos
de WAL). O parâmetro max_slot_wal_keep_size limita o tamanho de arquivos WAL retidos por
slots de replicação.
De forma parecida, os parâmetros hot_standby_feedback e vacuum_defer_cleanup_age fornecem
proteção contra tuplas que são relevantes sendo removidas por vacuum, mas o primeiro não
protege enquanto o standby estiver desconectado e o segundo frequentemente precisa ser
ajustado para um alto valor para proteger adequadamente.
O uso de slots de replicação superam essas desvantagens.
374 15. Replicação

Replicação física via streaming


A replicação física via streaming atua enviando e aplicando continuamente os registros de
WAL para servidores standby a fim de mantê-los atualizados. Um servidor standby se conecta
a um servidor primário ou a um outro servidor standby (replicação cascateada).
Para iniciar o servidor em modo standby (standby mode), é preciso, no diretório de dados
($PGDATA), criar o arquivo standby.signal. De forma parecida a quando se deseja colocar o
servidor em modo de recuperação, onde é usado outro arquivo, como o recovery.signal. Se
por acaso ambos forem criados, o modo standby terá prioridade.
Eis que surge a questão: qual é a diferença entre os arquivos standby.signal e recovery.signal?
O recovery.signal diz ao PostgreSQL para entrar em modo de recuperação de arquivamento
(archive recovery ) e o standby.signal diz ao PostgreSQL para entrar em modo standby.
Assim que o modo de recuperação finaliza tanto um como o outro arquivo são eliminados.
A replicação streaming é assíncrona por padrão. Nesse caso, há um pequeno atraso (delay ),
efetivando uma transação no servidor primário e para que mudanças estejam visíveis nos
servidores standbys.
Somente no servidor primário é possível fazer operações de leitura e escrita (rw), enquanto
que servidores secundários são somente leitura (ro).
Servidores upstream têm processos de envio de registros do WAL, os WAL senders enquanto
servidores standbys têm processos wal receivers. Para cada standby conectado ao servidor
upstream deve haver um processo wal sender.

Fig. 15.2: Replicação física via streaming


375 15. Replicação

Preparação do laboratório de replicação streaming

Hostname Papel IP
srv0.local Primário 192.168.56.70
srv1.local Standby 192.168.56.71

Configure o IP em cada máquina conforme a interface de rede e a forma de configuração da


distribuição Linux utilizada.
Para as configurações de hostname, basta fazer como seguem os comandos conforme o servi-
dor.

[#] [srv0] Configure o hostname da máquina do servidor primário:

hostnamectl set-hostname srv0.local

[#] [srv1] Configure o hostname da máquina do servidor standby:

hostnamectl set-hostname srv1.local

Reinicie ambos os servidores e os acesse novamente via SSH com os novos IPs.

Configurações relativas à replicação streaming

• cluster_name
Define um nome para a instância, que serve para vários propósitos. É o nome que
aparece no título de processo para todos processos dessa instância. É usado como
o nome de aplicação (application name) para conexão standby (veja o parâmetro
synchronous_standby_names).

• synchronous_commit
Define se a efetivação da transação aguardará que os registros do WAL sejam gravados
no armazenamento antes que o comando retorne uma indicação de esse comando foi
bem sucedido.
É um parâmetro enumerado que aceita somente os valores local, remote_write,
remote_apply, on e off.
376 15. Replicação

Se o parâmetro synchronous_standby_names não for vazio, synchronous_commit vai controlar


também se efetivações de transações vão aguardar pelos seus registros do WAL serem
processados nos standbys.

Resumo a seguir de todos os modos:

A) Commit persistente localmente

B) Commit de standby persistente após crash do Postgres

C) Commit de standby persistente após crash do sistema operacional

D) Consistência de consulta em standby

Modo A B C D
remote_apply Sim Sim Sim Sim
on Sim Sim Sim
remote_write Sim Sim
local Sim
off

É importante salientar que quanto mais garantias o modo tiver, mais as efetivações vão
demorar.

• max_wal_senders
Número máximo de conexões de replicação para standbys ou ferramentas que utilizem
esse tipo de conexão, como o pg_basebackup, por exemplo.

• max_replication_slots
Especifica o número máximo de slots de replicação.

• wal_keep_size
Determina o tamanho mínimo de segmanos de WAL anteriores a serem mantidos no
diretório do WAL caso o standby precise buscá-los para replicação via streaming.

• max_slot_wal_keep_size
Tamanho máximo que slots de replicação estão permitidos a manter no diretório do
WAL no momento de checkpoint.

• synchronous_standby_names
377 15. Replicação

Lista de standbys para replicação síncrona.


Os nomes contidos na lista são definidos no parâmetro cluster_name em cada standby.

• vacuum_defer_cleanup_age
Especifica a quantidade de transações pelas quais VACUUM e HOT updates vão adiar
a limpeza de versões de linhas mortas.

• primary_conninfo
String de conexão do standby para o master.

• primary_slot_name
Nome do slot de replicação para ser usado quando se conecta ao primário.

• hot_standby_feedback
Determina se envia ou não um retorno (feedback) ao master ou standby logo acima
(em caso de cascateamento) sobre consultas que estão sendo executadas no momento
no standby.
Pode ser usado para eliminar cancelamento de consultas causado por limpeza de reg-
istros, mas que causar inchaço (bloat) no primário para algumas cargas.
Se for o caso de uma replicação em cascata, o feedback é passado até que se alcance o
primário.

Funções relativas à replicação streaming

• pg_create_physical_replication_slot
Cria slot de de replicação.

• pg_drop_replication_slot
Remove slot de de replicação.

• pg_is_in_recovery
Retorna true se há recuperação em progresso.

• pg_last_wal_receive_lsn
Retorna a última localização WAL recebida e sincronizada para disco por replicação
streaming.

• pg_last_wal_replay_lsn
Retorna a última localização WAL replicada durante recuperação.

• pg_last_xact_replay_timestamp
378 15. Replicação

Retorna o timestamp da última transação replicada durante recuperação.


379 15. Replicação

Procedimentos para replicação via streaming assíncrona

[$] [srv0/srv1] Informar a versão majoritária do PostgreSQL:

read -p 'Versão majoritária do PostgreSQL: ' PGMAJOR

[$] [srv0/srv1] Criação de diretório de configuração no mesmo nível de $PGDATA:

mkdir -pm 0700 /var/local/pgsql/${PGMAJOR}/conf.d

[$] [srv0/srv1] Criação de arquivo de configuração extra e ajuste de modo de permissão:

touch /var/local/pgsql/${PGMAJOR}/conf.d/rep.conf && \


chmod 0600 /var/local/pgsql/${PGMAJOR}/conf.d/rep.conf

[$] [srv0] Edição do arquivo principal de configuração:

vim ${PGDATA}/postgresql.conf

password_encryption = scram-sha-256
wal_level = replica
max_wal_senders = 3
max_replication_slots = 2
include_dir = '/var/local/pgsql/<VERSÃO MAJORITÁRIA>/conf.d'

[$] [srv0] Conteúdo para o arquivo de conguração extra:

cat << EOF > /var/local/pgsql/${PGMAJOR}/conf.d/rep.conf


cluster_name = 'srv0'
primary_slot_name = 'rs_srv0'
EOF
380 15. Replicação

[$] [srv0] Verificar o contexto dos parâmetros:

psql -qc "


SELECT name, context
FROM pg_settings
WHERE name IN (
'password_encryption',
'wal_level',
'max_replication_slots',
'cluster_name'
);
"

name | context
-----------------------+------------
cluster_name | postmaster
max_replication_slots | postmaster
password_encryption | user
wal_level | postmaster

Por ter contexto postmaster, é necessário reiniciar o serviço.

[$] [srv0] Reiniciar o serviço para aplicar alterações de configuração:

pg_ctl restart

[$] [srv0] Criar um usuário para fins de replicação:

psql -Atqc "


CREATE USER user_rep
WITH REPLICATION
ENCRYPTED PASSWORD '123';
"

[>] [srv0] Criar um slot de replicação:

psql -Atqc "SELECT pg_create_physical_replication_slot('rs_srv1');"


381 15. Replicação

[$] [srv0] Adicionar novas linhas no pg_hba.conf para todos os nós da replicação:

cat << EOF >> ${PGDATA}/pg_hba.conf

# App connection =============================================================


host postgres user_rep 192.168.56.70/32 scram-sha-256
host postgres user_rep 192.168.56.71/32 scram-sha-256

# Streaming replication ======================================================


host replication user_rep 192.168.56.70/32 scram-sha-256
host replication user_rep 192.168.56.71/32 scram-sha-256
EOF

[$] [srv0] Recarregar as configurações para aplicar o que foi feito no pg_hba.conf:

pg_ctl reload

[$] [srv1] Parar o serviço e apagar o $PGDATA:

pg_ctl stop && rm -fr ${PGDATA}

[$] [srv1] Via pg_basebackup trazer o $PGDATA do servidor primário:

# Variável de ambiente de URL de conexão


DBCONN='host=192.168.56.70 dbname=postgres user=user_rep password=123'

# Execução do pg_basebackup
pg_basebackup -D ${PGDATA} -Fp -Xs -P -R -d "${DBCONN}"

-D PGDATA.
-Fp Formato plano (p: não tar).
-Xs Método de inclusão de WAL stream (s).
-P Exibe o progresso.
-R Cria o arquivo standby.signal faz com que o PostgreSQL entre em modo standby e também
dentro do postgresql.auto.conf coloca a string de conexão ao master.
-d String de conexão.
382 15. Replicação

[$] [srv1] Criar o arquivo de configuração para conexão ao servidor primário:

cat << EOF > /var/local/pgsql/${PGMAJOR}/conf.d/rep.conf


cluster_name = 'srv1'
primary_slot_name = 'rs_srv1'
autovacuum = off
EOF

Por ser uma replicação física em que o que é replicado são os registros do WAL, o processo
de autovacuum é desnecessário em standbys.

[$] [srv1] Muda o permissionamento para somente o usuário dono poder ler e gravar, grupo e
outros não têm qualquer permissão:

chmod 0600 /var/local/pgsql/${PGMAJOR}/conf.d/rep.conf

[$] [srv1] Inicia o serviço:

pg_ctl start

[$] [srv1] Pergunta ao PostgreSQL se está em modo de recuperação:

psql -Atqc 'SELECT pg_is_in_recovery()'

[$] [srv0] Pergunta ao PostgreSQL se está em modo de recuperação:

psql -Atqc 'SELECT pg_is_in_recovery()'

[$] [srv0] Cria uma base de dados de teste:

time createdb db_teste


383 15. Replicação

real 0m0.595s
user 0m0.000s
sys 0m0.005s

[$] [srv1] Lista as bases de dados:

psql -Atqc 'SELECT datname FROM pg_database'

template1
template0
postgres
db_teste

Monitorando a replicação via streaming

Views de monitoramento de replicação via streaming

• pg_stat_replication
Uma linha por processo WAL sender, exibe estatísticas sobre replicação que o servidor
de envio coleta do standby.

• pg_replication_slots
Uma linha por slot de replicação que existem na instância.

• pg_stat_wal_receiver
Exibe apenas uma linha com estatísticas sobre o WAL receiver.

[$] [srv0] Verificar o status de replicação:

psql -qc 'SELECT * FROM pg_stat_replication'

pid | 793
usesysid | 16438
usename | user_rep
application_name | srv1
client_addr | 192.168.56.71
client_hostname |
client_port | 59830
backend_start | 2021-03-11 19:03:08.157981-03
backend_xmin |
state | streaming
sent_lsn | 0/60001DB0
write_lsn | 0/60001DB0
flush_lsn | 0/60001DB0
replay_lsn | 0/60001DB0
write_lag |
384 15. Replicação

flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
reply_time | 2021-03-11 19:03:53.858385-03
385 15. Replicação

[$] [srv0] Verificar status de slots de replicação:

cat << EOF | psql -q


\x on
SELECT * FROM pg_replication_slots;
EOF

slot_name | rs_srv1
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | t
active_pid | 793
xmin |
catalog_xmin |
restart_lsn | 0/60001DB0
confirmed_flush_lsn |
wal_status | reserved
safe_wal_size |

[$] [srv1] Verificar informações a respeito de recebimento de dados WAL via replicação stream-
ing:

psql -qc 'SELECT * FROM pg_stat_wal_receiver'

pid | 771
status | streaming
receive_start_lsn | 0/60000000
receive_start_tli | 1
written_lsn | 0/60001DB0
flushed_lsn | 0/60001DB0
received_tli | 1
last_msg_send_time | 2021-03-11 19:06:24.25615-03
last_msg_receipt_time | 2021-03-11 19:06:24.262388-03
latest_end_lsn | 0/60001DB0
latest_end_time | 2021-03-11 19:03:53.852802-03
slot_name | rs_srv1
sender_host | 192.168.56.70
sender_port | 5432
conninfo | user=user_rep password=******** . . .

[$] [srv1] Há quanto tempo o standby não recebe nada?:

psql -Atqc 'SELECT now() - pg_last_xact_replay_timestamp()'

00:02:11.925001
386 15. Replicação

[$] [srv0] Obter em bytes a diferença entre a localização de WAL atual e a última replicada:

psql -Atqc 'SELECT pg_current_wal_lsn() - replay_lsn FROM pg_stat_replication'

37732400

[$] [srv0] Obter humanamente legível a diferença entre a localização de WAL atual e a última
replicada:

psql -Atqc '


SELECT pg_size_pretty(
pg_current_wal_lsn() -
replay_lsn) FROM pg_stat_replication'

19 MB

[$] [srv1] Funções de informações de recebimento das últimas informações WAL via replicação
streaming:

psql -qc "


SELECT
-- Localização WAL recebida e sincronizada para disco
pg_last_wal_receive_lsn(),

-- Localização WAL replicada durante recuperação


pg_last_wal_replay_lsn(),

-- Timestamp da última transação replicada durante recuperação


pg_last_xact_replay_timestamp();
"

pg_last_wal_receive_lsn | pg_last_wal_replay_lsn | pg_last_xact_replay_timestamp


-------------------------+------------------------+-------------------------------
0/60001E98 | 0/60001E98 | 2021-03-11 19:03:33.354885-03
387 15. Replicação

Mudando o ambiente para replicação síncrona

[$] [srv0] Configurar no master:

cat << EOF >> /var/local/pgsql/${PGMAJOR}/conf.d/rep.conf


synchronous_commit = remote_apply
synchronous_standby_names = 'srv1'
EOF

[$] [srv0] Verificar o contexto dos parâmetros:

psql -Atqc "


SELECT context
FROM pg_settings
WHERE name IN (
'synchronous_commit',
'synchronous_standby_names');
"

user
sighup

Não exigem que seja dado um restart no serviço, um simples reload já é suficiente para
aplicá-los.

[$] [srv0] Recarregar as configurações:

pg_ctl reload
388 15. Replicação

[$] [srv0] Estatísticas da replicação:

psql -tqc 'SELECT * FROM pg_stat_replication'

pid | 793
usesysid | 16438
usename | user_rep
application_name | srv1
client_addr | 192.168.56.71
client_hostname |
client_port | 59830
backend_start | 2021-03-11 19:03:08.157981-03
backend_xmin |
state | streaming
sent_lsn | 0/9C2B8830
write_lsn | 0/9C2B8830
flush_lsn | 0/9C2B8830
replay_lsn | 0/9C2B8830
write_lag |
flush_lag |
replay_lag |
sync_priority | 1
sync_state | sync
reply_time | 2021-03-11 19:16:00.402276-03

[$] [srv0] Informações dos slots de replicação:

cat << EOF | psql -tq


\x on
SELECT * FROM pg_replication_slots;
EOF

slot_name | rs_srv1
plugin |
slot_type | physical
datoid |
database |
temporary | f
active | t
active_pid | 793
xmin |
catalog_xmin |
restart_lsn | 0/9C2B8830
confirmed_flush_lsn |
wal_status | reserved
safe_wal_size |
389 15. Replicação

[$] [srv0] Cria uma nova base de dados de teste:

time createdb db_teste2

real 0m1.093s
user 0m0.006s
sys 0m0.000s

Nota-se aqui uma ligeira diferença de tempo na criação da base de dados.


Por causa da replicação síncrona cada gravação será mais demorada do que na assíncrona.

[$] [srv1] Lista as bases de dados:

psql -Atqc 'SELECT datname FROM pg_database'

template1
template0
postgres
db_teste
db_teste2

Failover

O processo de failover ocorre quando um standby se torna primário após o primário original
ser dado como fora de serviço.
Um failover pode ser automático ou manual, esse último é o que será visto a seguir.

[$] [srv0] Para o serviço do Posgres no primário:

pg_ctl stop

[$] [srv1] Promove o standby a primário:

pg_ctl promote

[$] [srv1] Pergunta ao PostgreSQL se está em modo de recuperação:

psql -Atqc 'SELECT pg_is_in_recovery()'


390 15. Replicação

[$] [srv1] Apaga as bases de dados de teste:

dropdb db_teste
dropdb db_teste2

[$] [srv1] Lista as bases de dados:

psql -Atqc 'SELECT datname FROM pg_database'

template1
template0
postgres

[$] [srv1] Habilitando novamente o autovacuum via sed:

# autovacuum = on
sed 's/\(^autovacuum = off\)/#\1\nautovacuum = on/g' -i \
/var/local/pgsql/${PGMAJOR}/conf.d/rep.conf

[$] [srv0] Reload para habilitar o autovacuum:

pg_ctl reload

Failback

[$] [srv1] Criar um slot de replicação:

psql -Atqc "SELECT pg_create_physical_replication_slot’(’rs_srv0);"

[$] [srv0] Desabilitando o autovacuum no novo standby:

echo "autovacuum = off" >> /var/local/pgsql/${PGMAJOR}/conf.d/rep.conf


391 15. Replicação

[$] [srv0] Com o utilitário pg_rewind, fazer a sincronia de srv1 para srv0:

# Variável de ambiente de URL de conexão


DBCONN='host=192.168.56.71 dbname=postgres user=user_rep password=123'

# Sincronização
pg_rewind -P -R \
-D ${PGDATA} \
--source-server="${DBCONN}"

[$] [srv0] Inicialização do serviço:

pg_ctl start
392 15. Replicação

Replicação lógica
É um metódo de replicar dados de objetos e suas mudanças baseando-se em sua identidade
de replicação (normalmente uma chave primária).
O termo “lógica” é um contraste à replicação física que usa endereços de bloco exatos para
se fazer a replicação byte a byte. O PostgreSQL suporta ambos os mecanismos ao mesmo
tempo.
A replicação lógica permite um controle granular sobre replicação de dados e segurança.
Na replicação lógica, é usado o modelo de publicação (publication) e subscrição (subscription)
com um ou mais assinantes em uma ou mais publicações no modo publicador.
Replicação lógica de uma tabela normalmente começa com um snapshot dos dados na base
de dados publicadora (publisher ) e copiá-los para o assinante (subscriber ). Uma vez feito isso,
as mudanças no publicador são enviadas ao assinante em tempo real assim que ocorrerem.
O assinante aplica os dados na mesma ordem do publicador de forma que a consistência
transacional seja garantida para publicações dentro de uma única assinatura. Esse método de
replicação de dados é também conhecido como replicação transacional.

Casos de uso típicos para replicação lógica:

• Envio de mudanças incrementais em um único banco de dados ou subconjunto de um


banco de dados;

• Disparo de gatilhos para mudanças individuais assim que elas chegam ao assinante;

• Consolidar várias bases de dados em uma única, para fins analíticos, por exemplo;

• Replicação entre diferentes versões majoritárias do PostgreSQL;

• Replicação do PostgreSQL entre plataformas diferentes (de FreeBSD para Linux, por
exemplo);

• Dar acesso a dados replicados para diferentes grupos de usuários;

• Compartilhamento de um subconjunto de bases de dados entre múltiplas bases de dados.

A base de dados assinante se comporta da mesma forma em qualquer instância PostgreSQL


e pode ser usada como publicador para outras bases de dados, definindo suas próprias pub-
licações. Quando o assinante é tratado como uma aplicação somente leitura, não haverá
conflitos de uma única assinatura. Por outro lado, se houver outras escritas feitas ou por uma
aplicação ou por outros assinantes para o mesmo grupo de tabelas, haverá conflitos.
393 15. Replicação

Publicação

Uma publicação pode ser definida em qualquer servidor master de replicação física. O nó
onde uma publicação é definida é chamado de publicador. Uma publicação é um conjunto de
mudanças gerado de uma tabela ou um grupo de tabelas, e pode também ser descritq como
um conjunto de mudanças ou conjunto de replicação. Cada publicação existe em apenas uma
base de dados.
Publicações são diferentes de schemas e não afetam como uma tabela é acessada. Cada tabela
pode ser adicionada para múltiplas publicações se for necessário.
Atualmente, publicações podem conter apenas tabelas. Objetos devem ser adicionados ex-
plicitamente, exceto quando uma publicação é criada para ALL TABLES.
Uma tabela publicada deve ter uma “identidade de replicação” configurada para que operações
de UPDATE e DELETE sejam replicadas de forma que para modificar ou remover sejam devidamente
identificadas pelo lado do assinante. Por padrão, é a chave primária, se houver. Outro índice
único (com certos requerimentos adicionais) podem também ser configurados para ser iden-
tidade de réplica. Se a tabela não tiver uma chave adequada, então ela poderá ser definida
como identidade de réplica completa (replica identity full ), o que significa que toda linha se
tornará a chave. Porém, isso é muito ineficiente e deve apenas ser usado como um substituto
se não houver outra solução. Se uma identidade de réplica diferente de “full” for definida no
lado do publicador, uma identidade de réplica compreendendo as mesmas ou menos colunas
também deve ser definida no lado do assinante.
Se uma tabela sem uma identidade de réplica for adicionada à publicação que replica UPDATE
ou DELETE, quando houver um dos dois causará um erro no publicador.
Operações de INSERT são independentes de identidade de réplica.
Toda publicação pode ter vários assinantes.
Uma publicação é criada usando o comando CREATE PUBLICATION.
Tabelas individuais podem ser adicionadas ou removidas dinamicamente usando o comando
ALTER PUBLICATION.

Subscrição e gerenciamento de slots de replicação

Uma assinatura de uma replicação lógica é o lado que recebe os dados. O nó onde a assinatura
é definida é chamado de assinante (susbscriber ).
Uma assinatura define a conexão a outro banco de dados e conjunto de publicações (um ou
mais) para o qual ele se inscreve.
O banco de dados assinante se comporta da mesma maneira que qualquer outra instância
PostgreSQL e pode ser usado como publicador para outras bases de dados definindo suas
próprias publicações.
Cada assinatura ativa recebe mudanças de um slot de replicação do lado publicador. Normal-
mente, o slot de replicação é criado de forma automática quando a assinatura é criada usando
o comando CREATE SUBSCRIPTION e é removido também automaticamente quando a assinatura é
removida com DROP SUBSCRIPTION. Em algumas situações, no entanto, pode ser útil ou necessário
394 15. Replicação

manipular a assinatura e seu respectivo slot de replicação separadamente, como nos casos:

• Ao criar a assinatura, o slot de replicação já existe. Nesse caso, a assinatura pode ser
criada usando a opção create_slot = false para associar ao slot existente.

• Ao criar uma assinatura, o host publicador está fora de alcance ou em um estado


intermitente.
Então a assinatura pode ser cirada com a opção connect = false. O slot de replicação
terá que ser criado manualmente antes que a assinatura possa ser ativada.

• Quando se remove uma assinatura, mas o slot de replicação deve ser mantido.
Isso é útil quando o banco de dados assinante está sendo movido para um host difer-
ente e será ativado por lá. Nesse caso, desassociar o slot da assinatura usando ALTER
SUBSCRIPTION antes de tentar remover a assinatura.

• Ao remover uma assinatura, o host remoto não está alcançável. Nesse caso, desassociar
o slot da assinatura usando ALTER SUBSCRIPTION antes de tentar remover a assinatura.
Se a instância de base de dados remota não existir mais, nenhuma ação adicional será
necessária.
Se, porém, a instância remota estiver apenas inacesível, o slot de replicação deve então
ser removido manualmente, caso contrário ela continuaria a reservar WAL e eventual-
mente pode causar disco cheio.

Conflitos

Replicação lógica se comporta de forma parecida com operações DML em que os dados serão
atualizados mesmo que tenham sido alterados localmente no nó do assinante. Se os dados que
chegam violam constraints, a replicação vai parar. Isso é considerado um conflito. Ao replicar
operações de UPDATE ou DELETE, dados faltantes não produzirão um conflito e tais operações
serão ignoradas. Um conflito vai produzir um erro e vai parar a replicação; isso deve ser
resolvido manualmente. Maiores detalhes sobre conflitos podem ser encontrados nos logs do
serivor assinante.
A solução pode ser feita mudando dados no assinante de forma que não conflite com uma
alteração que venha ou ignorando a transação que cause conflitos com os dados existentes.
A transação pode ser ignorada chamando a função pg_replication_origin_advance() com o
parâmetro node_name correspondente ao nome do nó assinante e uma posição. A atual posição
de origem pode ser vista na view de sistema pg_replication_origin_status.
395 15. Replicação

Restrições

Autalmente, replicação lógica tem as seguintes restrições ou funcionalidades faltantes, que


pode mudar em futuros lançamentos:

• A parte estrutural da base de dados não é replicada, ou seja, comandos DDL não repli-
cados.
A estrutura inicial pode ser copiada via pg_dump --schema-only.
Mudanças posteriores na estrutura precisariam ser mantidas manualmente. Não há ne-
cessidade para as estruturas serem abolutamente as mesmas em ambos os lados. A
replicação lógica é robusta quando as definições de estrutura mudam em um banco de
dados ativo: quando a estrutura é alterada no publicador e os dados replicados começam
a chegar ao assinante, mas não se encaixam no estrutura da tabela, a replicação apre-
sentará um erro até que o estrutura seja corrigida.
Em muitos casos, erros intermitentes podem ser evitados aplicando alterações de estru-
tura aditivas primeiro ao assinante.

• Dados de sequências não são replicados. Os dados em campos serial ou em colunas


identity que são coberttas por sequências serão replicadas como parte da tabela, mas
a sequência por si só não, ainda mostraria o valor inicial no assinante. Se o assinante
é usado como uma base somente leitura isso não será um problema. Porém, se houver
uma mudança no ambiente em que o banco de dados assinante se tornar um master,
então as sequências precisarão ser atualizadas para seus últimos valores, ou copiando os
dados atuais do publicador (talvez usando pg_dump) ou determinando valores altos a
partir das próprias tabelas.

• Replicação do comando TRUNCATE é suportada, mas é preciso ter algum cuidado ao fazer
isso para trupos de tabelas conectadas por chaves estrangeiras.
Ao replicar um TRUNCATE, o assinante vai truncar o mesmo grupo de tabelas que foram
no publicador, ou explictamente via opção CASCADE, exceto tabelas que não fazem parte
da assinatura. Isso vai funcionar corretamente se todas tabelas afetadas forem parte
da mesma assinatura. Mas se algumas tabelas a serem truncadas no assinante tiverem
ligações de chave estrangeira para tabelas que não fazem parte da mesma (ou qualquer)
assinatura, então a aplicação de truncamento no assinante vai falhar.

• Grandes objetos (large objects) não são replicados. Não há solução para isso, a não ser
armazenar dados em tabelas normais.

• Replicação é suportada apenas por tabelas, incluindo as particionadas.


Tentativas de replicar outros tipos de relações como views (materializadas ou não) ou
tabelas estrangeiras, vão falhar.
396 15. Replicação

• Ao replicar tabelas particionadas, os dados vêm das partições no publicador. Essas


partições devem existir tambem no assinante.

Arquitetura

Snapshot inicial

A replicação lógica começa pela cópia do instantâneo (snapshot) dos dados em uma base
publicadora.
Uma vez que isso é feito, mudanças no publicador serão enviadas ao assinante em tempo
real. O assinante aplica os dados na ordem que as efetivações foram feitas no publicador para
consistência transacional.
A replicação lógica é construída com uma arquitetura similar à replicação física via streaming.
É implementada por walsender e aplicar processos.

Monitoramento

Por causa de sua similaridade na arquitetura com a replicação física via streaming, o moni-
toramento em um nó de publicação é similar a monitorar um master de replicação física.
Informações de monitoramento sobre assinatura estão na view pg_stat_subscription. Essa view
contém uma linha para cada worker assinante.
Uma assinatura pode ter zero ou mais workers ativos de assinatura dependendo de seu estado.
Normalmente, há um único processo de aplicação rodando para uma assinatura habilitada.
Uma assinatura desabilitada ou uma assinatura quebrada não terá linhas nessa view.
Se os dados iniciais de sincronização de qualquer tabela estiverem em progresso, haverá workers
adicionais para as tabelas sendo sincronizadas.

Configuração do publicador

wal_level = logical
max_replication_slots= pelo menos o número de assinaturas esperadas para conectar mais
alguma reserva para sincronização de tabela.
max_wal_senders = max_replication_slots + réplicas físicas, se estiverem conectadas ao mesmo
tempo

Configuração do assinante
max_replication_slots = número de assinaturas
max_logical_replication_workers = pelo menos o número de assinaturas + reserva para sin-
cronização de tabela.
max_worker_processes = pelo menos max_logical_replication_workers + 1
397 15. Replicação

Preparação do laboratório

Hostname Papel IP
srv0.local Publicador 192.168.56.70
srv1.local Assinante 192.168.56.71

Configure o IP em cada máquina conforme a interface de rede e a forma de configuração da


distribuição Linux utilizada.
Para as configurações de hostname basta fazer como seguem os comandos conforme o servidor.

[#] [srv0] Configure o hostname da máquina do servidor primário:

hostnamectl set-hostname srv0.local

[#] [srv1] Configure o hostname da máquina do servidor standby:

hostnamectl set-hostname srv1.local

Reinicie ambos os servidores e os acesse novamente via SSH com os novos IPs.

Procedimentos e testes para replicação lógica

[$] [srv0] Edição de parâmetros no publicador:

vim ${PGDATA}/postgresql.conf

password_encryption = scram-sha-256
wal_level = logical
max_replication_slots = 10
max_wal_senders = 10

[$] [srv0] No publicador, adicionar uma nova linha ao pg_hba.conf para permitir acesso do
assinante:
398 15. Replicação

echo 'host all all 192.168.56.71/32 scram-sha-256' >> ${PGDATA}/pg_hba.conf

[$] [srv1] Edição de parâmetros no assinante:

vim ${PGDATA}/postgresql.conf

password_encryption = scram-sha-256
max_replication_slots = 10
max_logical_replication_workers = 10
max_worker_processes = 8

[$] [srv0/srv1] Reiniciar ambos os servidores para aplicar as configurações:

pg_ctl restart

[$] [srv0/srv1] Em ambos os servidores criar um usuário:

psql -c "CREATE ROLE user_teste WITH REPLICATION LOGIN PASSWORD '123';"

[$] [srv0/srv1] Em ambos os servidores criar um banco de dados:

psql -qc 'CREATE DATABASE db_teste OWNER user_teste;'

[$] [srv0/srv1] Em ambos os servidores criar uma tabela:

psql -d db_teste -U user_teste -qc 'CREATE TABLE tb_teste(campo int);'

[$] [srv0] Criar uma publicação para todas tabelas:

psql -d db_teste -qc \


'CREATE PUBLICATION pb_teste FOR ALL TABLES;'
399 15. Replicação

[$] [srv1] Criação de uma assinatura:

psql -d db_teste -qc "


CREATE SUBSCRIPTION sb_teste
CONNECTION 'host=192.168.56.70
port=5432
dbname=db_teste
user=user_teste
password=123'
PUBLICATION pb_teste;
"

[$] [srv0] Inserir dados em uma tabela no publicador:

psql -d db_teste -Atqc \


'INSERT INTO tb_teste SELECT generate_series(1, 5)'

[$] [srv1] Verificar os dados no assinante:

psql -d db_teste -Atqc \


'SELECT campo FROM tb_teste;'

1
2
3
4
5

[$] [srv0/srv1] Em ambos os servidores criar uma nova tabela:

psql -d db_teste -U user_teste -qc 'CREATE TABLE tb_teste2(campo int);'

[$] [srv0] Inserir dados na nova tabela no publicador:

psql -d db_teste -Atqc \


'INSERT INTO tb_teste2 SELECT generate_series(1, 5)'
400 15. Replicação

[$] [srv1] Verificar os dados no assinante:

psql -d db_teste -Atqc \


'SELECT campo FROM tb_teste2;'

Nenhum resultado.
É preciso dar um refresh na assinatura.

[$] [srv1] Refresh na assinatura:

psql -d db_teste -Atqc \


'ALTER SUBSCRIPTION sb_teste REFRESH PUBLICATION;'

[$] [srv1] Verificar novamente os dados no assinante:

psql -d db_teste -Atqc \


'SELECT campo FROM tb_teste2;'

1
2
3
4
5

Após o refresh os dados se tornaram disponíveis no assinante.


401 15. Replicação

Soluções de terceiros para alta disponibilidade no PostgreSQL


repmgr

O repmgr é uma ferramenta open source, desenvolvida inicialmente pela 2ndQuadrant (foi
adquirida pela EDB), para gerenciamento de clusters de replicação PostgreSQL.
O repmgr traz facilidades de monitoramento de um cluster de replicação e dentre seus recursos
destaca-se o failover automático.

https://repmgr.org

Patroni

Patroni é um gerenciador de cluster usado para personalizar e automatizar a implementação


e manutenção de clusters PostgreSQL de alta disponilidade.
O Patroni utiliza armazenamento de configuração distribuído como etcd, Consul, ZooKeeper
ou Kubernetes.
É uma solução de alta disponibilidade para PostgreSQL com recursos nativos para nuvem
(cloud native) e opções avançadas para failover e failback.

https://github.com/zalando/patroni

PAF - PostgreSQL Automatic Failover

É um agente de recursos PostgreSQL para Pacemaker. Seu objetivo original é manter a


administração do PostgreSQL e do Pacemaker de forma limpa, para manter as coisas simples,
muito documentadas e poderosas.

https://github.com/ClusterLabs/PAF

CitusDB

É uma solução de banco de dados distribuído que é otimizado para cargas de trabalho em
tempo real.
O CitusDB escala o PostgreSQL através de um cluster de servidores via sharding e replicação.
Seu avançado motor de consultas paraleliza consultas vindas desses servidores para respostas
em tempo real.
O CitusDB se recupera de falhas fazendo failover automaticamente, assim fazendo alta disponi-
bilidade.
O CitusDB não é um fork do PostgreSQL, mas sim o extende.

https://www.citusdata.com
402 15. Replicação

Greenplum

Greenplum Database (GPDB) é um avançado data warehouse com muitos recursos de código
aberto baseado em PostgreSQL.
Ele fornece análises poderosas e rápidas em volumes de dados em escala de petabyte.
Voltado exclusivamente para análise big data, o Greenplum Database tem um otimizador de
consultas baseado em custos avançado que entrega alto desempenho de consultas analíticas
em grandes volumes de dados.

https://github.com/greenplum-db/gpdb

YugabyteDB

O YugabyteDB um fork do PostgreSQL, uma solução de banco de dados distribuído para dar
poder a aplicações globais em escala de Internet.
Pode ser implementado em nuvens privadas ou públicas bem como ambientes Kubernetes com
facilidade.
Ele tem também um certo grau de compatibilidade com o Apache Cassandra, que permite que
aplicações escritas para ele utilizem o YugaByteDB.
É uma solução com muitas características cloud native.

https://www.yugabyte.com
403 15. Replicação

——————– EXTRA ——————–

Replicação e alta disponibilidade com repmgr


O repmgr é uma estrutura que facilita o gerenciamento e monitoramento da replicação física
via streaming.
Dentre suas facilitades, possui failover automático.

Witness

Diferente de servidores dos tipos primário e standby, apesar de também ser uma instância
PostgreSQL, não faz parte do cluster de replicação.
Seu objetivo é guardar metadados do cluster de replicação repmgr e ficar vigiando, ser “teste-
munha” em caso de falha do servidor primário e fazer com que um dos standbys assuma como
primário, no processo de promover o failover automático.

Ambiente de laboratório

Para o laboratório, será preciso 4 (quatro) máquinas.


Será utilizada a instalação do PostgreSQL feita via compilação do código-fonte.
Configurar o hostname e IP conforme a tabela:

Informações dos hosts do cluster de replicação

Hostname Papel IP Node ID


alpha.local Primário 192.168.56.71 1
beta.local Standby 192.168.56.72 2
gamma.local Standby 192.168.56.73 3
omega.local Witness 192.168.56.74 4
404 15. Replicação

[#] [todos] De acordo com o planejado de laborário, adicionar linhas no arquivo /etc/hosts:

cat << EOF >> /etc/hosts

# Cluster repmgr =============================================================


192.168.56.71 alpha alpha.local
192.168.56.72 beta beta.local
192.168.56.73 gamma gamma.local
192.168.56.74 omega omega.local
EOF

[#] [todos] Configure o hostname de cada nó do cluster de replicação:

hostnamectl set-hostname <hostname>

Configure também o endereço IP conforme a distribuição Linux utilizada.

[#] [todos] Reinicie o servidor:

init 6

Arquitetura de diretórios

Tipo Localização Variável de ambiente


Instalação do PostgreSQL /usr/local/pgsql/<versão majoritária> PG_HOME
Dados (PGDATA) /var/local/pgsql/<versão majoritária>/data PGDATA
Binários /usr/local/pgsql/<versão majoritária>/bin PGBIN
Instalação do repmgr /usr/local/repmgr/<versão majoritária> REPMGR_HOME

Instalação do repmgr via compilação do código-fonte

[#] [todos] Variável de ambiente para versão majoritária do PostgreSQL (como root):

read -p \
'Digite a versão majoritária: ' \
PGMAJOR
405 15. Replicação

[#] [todos] Configuração de outras variáveis de ambiente:

export PG_HOME="/usr/local/pgsql/${PGMAJOR}"
export PGDATA="/var/local/pgsql/${PGMAJOR}/data"
export PGBIN="${PG_HOME}/bin"
export REPMGR_HOME="/usr/local/repmgr/${PGMAJOR}"
export PATH="${PGBIN}:${PATH}"

[#] [todos] Pacotes necessários:

export PKG="\
gcc \
make \
flex \
libxslt1-dev \
libxml2-dev \
libselinux1-dev \
libpam0g-dev \
libssl-dev \
libkrb5-dev \
libedit-dev \
zlib1g-dev \
libreadline-dev
"

[#] [todos] Atualizar o repositório, instalar os pacotes e fazer a limpeza dos pacotes baixados:

apt update && apt install -y wget ${PKG} && apt clean

[#] [todos] Digite a versão (X.Y.Z) do repmgr a ser baixada:

read -p 'Digite a versão (X.Y.Z) do repmgr a ser baixada: ' REPMGR_VERSION

[#] [todos] URL do arquivo do repmgr:

export REPMGR_URL=\
"https://repmgr.org/download/repmgr-${REPMGR_VERSION}.tar.gz"
406 15. Replicação

[#] [todos] Baixar o repmgr:

wget -c ${REPMGR_URL} -P /tmp/

[#] [todos] Ir ao diretório onde o arquivo foi baixado, descompactá-lo e acessar o diretório:

cd /tmp/ && tar xvf repmgr-${REPMGR_VERSION}.tar.gz && cd repmgr-${REPMGR_VERSION}

[#] [todos] configure, compilação e instalação:

./configure --prefix /usr/local/repmgr/${PGMAJOR} && make && make install

[#] [todos] Após a compilação ter sido bem sucedida, remover os pacotes instalados:

apt purge -y ${PKG}

[#] [todos] Criação do diretório de configuração e de logs do repmgr:

mkdir -pm 700 {/etc,/var/log}/repmgr/${PGMAJOR}

[#] [todos] Dar propriedade ao usuário e grupo postgres:

chown -R postgres: {/etc,/var/log}/repmgr ~postgres/


407 15. Replicação

[#] [todos] Criação do arquivo de serviço do repmgrd:

cat << EOF > /lib/systemd/system/repmgrd-${PGMAJOR}.service


[Unit]
Description=A replication manager, and failover management tool for PostgreSQL
After=syslog.target
After=network.target
After=postgresql.service
[Service]
Type=forking
User=postgres
Group=postgres
PIDFile=/var/run/repmgr/${PGMAJOR}/repmgrd.pid
# Where to send early-startup messages from the server
# This is normally controlled by the global default set by systemd
# StandardOutput=syslog
ExecStart=${PGBIN}/repmgrd \
-f /etc/repmgr/${PGMAJOR}/repmgr.conf -p /var/run/repmgr/${PGMAJOR}/repmgrd.pid -d --
verbose
ExecStop=kill -TERM \`cat /var/run/repmgr/${PGMAJOR}/repmgrd.pid\`
ExecReload=kill -HUP \`cat /var/run/repmgr/repmgrd.pid\`
# Give a reasonable amount of time for the server to start up/shut down
TimeoutSec=300
[Install]
WantedBy=multi-user.target
EOF

[#] [todos] Habilitando o serviço repmgrd:

systemctl enable repmgrd-${PGMAJOR}.service

[#] [todos] Criação de arquivo para criação de diretório temporário para arquivo de pid:

cat << EOF > /usr/lib/tmpfiles.d/repmgr-${PGMAJOR}.conf


d /var/run/repmgr/${PGMAJOR} 0755 postgres postgres -
EOF

[#] [todos] Cria o diretório temporário:

systemd-tmpfiles --create
408 15. Replicação

[#] [todos] Variável de ambiente para a localização do arquivo de configuração:

echo "export REPMGRDCONF='/etc/repmgr/${PGMAJOR}/repmgr.conf'" >> \


~postgres/.pgvars

Configurações de nós

[#] [todos] Variável de ambiente para o Node ID (inteiro maior que zero):

read -p 'Digite o ID do nó: ' NODEID

[#] [todos] Criar arquivo vazio e mudar proprietário:

# Criar arquivo vazio


touch /etc/repmgr/${PGMAJOR}/repmgr.conf

# Mudar propriedade recursivamente do diretório do repmgr


chown -R postgres: /etc/repmgr

[#] [alpha/beta/gamma] Criação do arquivo principal do repmgr:

cat << EOF > /etc/repmgr/${PGMAJOR}/repmgr.conf


node_id=${NODEID}
node_name='`hostname`'
conninfo='host=`hostname` user=user_repmgr dbname=db_repmgr\
connect_timeout=2'
data_directory='${PGDATA}'
pg_bindir='${PGBIN}'
use_replication_slots='yes'
witness_sync_interval=15
# repmgrd
failover=automatic
promote_command='${PGBIN}/repmgr standby promote\
-f /etc/repmgr/${PGMAJOR}/repmgr.conf --log-to-file'
follow_command='${PGBIN}/repmgr standby follow\
-f /etc/repmgr/${PGMAJOR}/repmgr.conf --log-to-file --upstream-node-id=%n'
# Log
log_level=INFO
log_file='/var/log/repmgr/${PGMAJOR}/repmgrd.log'
log_status_interval=300
EOF

[#] [alpha/omega] IPs dos servidores do cluster:


409 15. Replicação

read -p 'Digite os IPs dos nós (separados por um espaço): ' IP

Digite os IPs dos nós (separados por um espaço): 192.168.56.71 192.168.56.72 192.168.56.73
192.168.56.74

[#] [alpha/omega] Adicionar linhas ao pg_hba.conf:

# Cabeçalho:
cat << EOF >> ${PGDATA}/pg_hba.conf

# REPMGR ======================================================================
# repmgr database
EOF

# Para cada IP, adicionar uma linha referente ao banco db_repmgr:


for i in ${IP}
do
echo "host db_repmgr user_repmgr ${i}/32 trust" >> \
${PGDATA}/pg_hba.conf
done

# Pula uma linha e adiciona um cabeçalho referente à replicação:


cat << EOF >> ${PGDATA}/pg_hba.conf

# Replication
EOF

# Para cada IP, adicionar uma linha referente à replicação:


for i in ${IP}
do
echo "host replication user_repmgr ${i}/32 trust" >> \
${PGDATA}/pg_hba.conf
done

# Adiciona linha de encerramento:


cat << EOF >> ${PGDATA}/pg_hba.conf
# =============================================================================
EOF
410 15. Replicação

[#] [alpha/omega] Verificando o final do arquivo pg_hba.conf (últimas 14 linhas):

tail -14 ${PGDATA}/pg_hba.conf

# REPMGR ======================================================================
# repmgr database
host db_repmgr user_repmgr 192.168.56.71/32 trust
host db_repmgr user_repmgr 192.168.56.72/32 trust
host db_repmgr user_repmgr 192.168.56.73/32 trust
host db_repmgr user_repmgr 192.168.56.74/32 trust
# Replication
host replication user_repmgr 192.168.56.71/32 trust
host replication user_repmgr 192.168.56.72/32 trust
host replication user_repmgr 192.168.56.73/32 trust
host replication user_repmgr 192.168.56.74/32 trust
# =============================================================================

[#] [alpha] Alterar os seguintes parâmetros do postgresql.conf:

vim ${PGDATA}/postgresql.conf

listen_addresses = '*'
wal_level = replica
archive_mode = on
archive_command = '/bin/true'
max_wal_senders = 10
max_replication_slots = 10
hot_standby = on
shared_preload_libraries = 'repmgr'

[#] [omega] Alterar os seguintes parâmetros do postgresql.conf:

vim ${PGDATA}/postgresql.conf

listen_addresses = '*'
shared_preload_libraries = 'repmgr'

[#] [alpha/omega] Reiniciar o serviço do PostgreSQL:

systemctl restart postgresql-${PGMAJOR}.service


411 15. Replicação

[$] [alpha/omega] Criar role e database para o repmgr:

psql << EOF


-- Criação do usuário do repmgr
CREATE ROLE user_repmgr
REPLICATION
LOGIN
SUPERUSER;

-- Criação da base de dados (metadados) do repmgr:


CREATE DATABASE db_repmgr OWNER user_repmgr;
EOF

SSH sem senha

[#] [todos] Criando as chaves SSH para o usuário postgres:

su - postgres -c "ssh-keygen -P '' -t rsa -f ~/.ssh/id_rsa"

[#] [todos] Criar um arquivo com todos os IPs e hostnames possíveis de cada nó do cluster
de replicação:

cat << EOF > /tmp/nodes.txt


192.168.56.71
192.168.56.72
192.168.56.73
192.168.56.74
alpha
beta
gamma
omega
alpha.local
beta.local
gamma.local
omega.local
EOF

[#] [todos] Variável de ambiente dos nós:

export NODES=`cat /tmp/nodes.txt`

É o resultado da saída do comando que expõe o conteúdo do arquivo.


412 15. Replicação

[#] [todos] Envio da chave pública de postgres via root:

for NODE in ${NODES}; do


cat ~postgres/.ssh/id_rsa.pub | \
xargs -i ssh ${NODE} \
"su - postgres -c 'echo {} >> ~postgres/.ssh/authorized_keys'"
su - postgres -c "ssh -oStrictHostKeyChecking=no ${NODE} hostname"
done

Nó primário

[$] [alpha] Registrar o nó como primário:

repmgr -f ${REPMGRDCONF} primary register

INFO: connecting to primary database...


NOTICE: attempting to install extension "repmgr"
NOTICE: "repmgr" extension successfully installed
NOTICE: primary node record (ID: 1) registered

[$] [alpha] Verifique informações do cluster:

repmgr -f ${REPMGRDCONF} cluster show

ID | Name | Role | Status | Upstream | Location | Priority | Timeline | Connection string


----+-------------+---------+-----------+----------+----------+----------+----------+-------------------------
1 | alpha.local | primary | * running | | default | 100 | 1 | host=alpha.local user=us

[$] [alpha] É possível obter a mesma informação via consulta no banco:

psql -qc 'SELECT * FROM repmgr.nodes;' -h alpha -U user_repmgr db_repmgr

node_id | 1
upstream_node_id |
active | t
node_name | alpha.local
type | primary
location | default
priority | 100
conninfo | host=alpha.local user=user_repmgr dbname=db_repmgr connect_timeout=2
repluser | user_repmgr
slot_name | repmgr_slot_1
config_file | /etc/repmgr/13/repmgr.conf
413 15. Replicação

Nós standbys

[$] [beta/gamma] Pare o serviço do PostgreSQL:

pg_ctl -m i stop

[$] [beta/gamma] Clonar o nó primário:

repmgr -c -h alpha -U user_repmgr -d db_repmgr\


-f ${REPMGRDCONF} -F standby clone

[$] [beta/gamma] Inicie o serviço do PostgreSQL:

pg_ctl start

[$] [beta/gamma] Registrando o nó como standby:

repmgr -f ${REPMGRDCONF} standby register

INFO: connecting to local node "beta.local" (ID: 2)


INFO: connecting to primary database
WARNING: --upstream-node-id not supplied, assuming upstream node is primary (node ID 1)
INFO: standby registration complete
NOTICE: standby node "beta.local" (ID: 2) successfully registered

INFO: connecting to local node "gamma.local" (ID: 3)


INFO: connecting to primary database
WARNING: --upstream-node-id not supplied, assuming upstream node is primary (node ID 1)
INFO: standby registration complete
NOTICE: standby node "gamma.local" (ID: 3) successfully registered
414 15. Replicação

Nó witness

[#] [omega] Criação do arquivo de configuração do repmgr:

cat << EOF > /etc/repmgr/${PGMAJOR}/repmgr.conf


node_id=${NODEID}
node_name='`hostname`'
conninfo='host=`hostname` user=user_repmgr dbname=db_repmgr connect_timeout=2'
data_directory='${PGDATA}'
pg_bindir='${PGBIN}'
use_replication_slots='yes'
# Log
log_level=INFO
log_file='/var/log/repmgr/${PGMAJOR}/repmgrd.log'
log_status_interval=300
EOF

A questão do database system identifier

Database system identifier é um inteiro de 64 bits cujo propósito de ser uma identificação que
espera-se ser única para a instância.
Em casos que ao provisionar uma nova máquina e esse provisionamento for baseado em uma
máquina modelo (template), todos servidores PostgreSQL (se o mesmo tiver sido instalado
nesse modelo) terão a mesma identificação de instância.
O repmgr não permite que o servidor witness seja a mesma instância do servidor primário,
então ele faz a validação disso utilizando o database system identifier.
Para contornar esse problema, antes de registrar o nó como witness , execute alguns procedi-
mentos, inclusive apagar a instância e recriá-la com o initdb.

[$] [omega] Variável de ambiente para versão majoritária do PostgreSQL:

read -p \
'Digite a versão majoritária: ' \
PGMAJOR

[$] [omega] Dump da instância:

pg_dumpall > /tmp/omega.sql


415 15. Replicação

[$] [omega] Copiar os arquivos postgresql.conf e pg_hba.conf originais:

cp -v ${PGDATA}/{postgresql,pg_hba}.conf /tmp/

'/var/local/pgsql/data/13/postgresql.conf' -> '/tmp/postgresql.conf'


'/var/local/pgsql/data/13/pg_hba.conf' -> '/tmp/pg_hba.conf'

[$] [omega] Pare o serviço do PostgreSQL:

pg_ctl -m i stop

[$] [omega] Eliminar os diretórios de dados e do WAL:

rm -fr /var/local/pgsql/${PGMAJOR}/{wal,data}

[$] [omega] Recriar a instância com o mesmo diretório de WAL anterior:

initdb \
-D ${PGDATA} \
-E utf8 \
-U postgres \
-k \
--locale=pt_BR.utf8 \
--lc-collate=pt_BR.utf8 \
--lc-monetary=pt_BR.utf8 \
--lc-messages=en_US.utf8 \
-T portuguese \
-X /var/local/pgsql/${PGMAJOR}/wal

[$] [omega] Substituir os arquivos de configuração postgresql.conf e pg_hba.conf pelos ante-


riores:

mv /tmp/{postgresql,pg_hba}.conf ${PGDATA}/

[$] [omega] Inicie o serviço do PostgreSQL:


416 15. Replicação

pg_ctl start

[$] [omega] Restaurar o dump do cluster:

psql -f /tmp/omega.sql

[$] [omega] Registrar o servidor como witness:

repmgr \
-d db_repmgr \
-U user_repmgr \
-h alpha \
-f ${REPMGRDCONF} \
--verbose witness register

NOTICE: using provided configuration file "/etc/repmgr/13/repmgr.conf"


INFO: connecting to witness node "omega.local" (ID: 4)
INFO: connecting to primary node
NOTICE: attempting to install extension "repmgr"
NOTICE: "repmgr" extension successfully installed
INFO: witness registration complete
NOTICE: witness node "omega.local" (ID: 4) successfully registered

Failover

[#] [todos] Em todos os nós como root, dar restart no serviço repmgrd e em seguida verificar
seu estado:

systemctl restart repmgrd-${PGMAJOR} && \


systemctl status repmgrd-${PGMAJOR}

[#] [alpha] Propositalmente parar o nó primário:

systemctl stop postgresql-${PGMAJOR}.service

[$] [omega] Acompanhar os logs do witness:

tail -F /var/log/repmgr/${PGMAJOR}/repmgrd.log
417 15. Replicação

. . .
[2021-03-18 11:37:05] [DETAIL] following new primary "beta.local" (ID: 2)
[2021-03-18 11:37:05] [INFO] searching for primary node
[2021-03-18 11:37:05] [INFO] checking if node 2 is primary
[2021-03-18 11:37:05] [INFO] current primary node is 2
[2021-03-18 11:37:05] [INFO] witness monitoring connection to primary node "beta.local" (ID: 2)

[$] [omega] Informações do cluster:

repmgr -f ${REPMGRDCONF} cluster show

ID | Name | Role | Status | Upstream | Location | Priority | Timeline | Connection string


----+-------------+---------+-----------+------------+----------+----------+----------+-----------------------
1 | alpha.local | primary | - failed | ? | default | 100 | | host=alpha.local user=
2 | beta.local | primary | * running | | default | 100 | 2 | host=beta.local user=u
3 | gamma.local | standby | running | beta.local | default | 100 | 2 | host=gamma.local user=
4 | omega.local | witness | * running | beta.local | default | 0 | n/a | host=omega.local user=
WARNING: following issues were detected
- unable to connect to node "alpha.local" (ID: 1)
HINT: execute with --verbose option to see connection error messages

Pelos logs e pelo comando cluster show do repmgr verificou-se que um dos standbys, o servidor
beta assumiu como primário.

Failback

[$] [alpha] Reintegrar o nó ao cluster de replicação:

repmgr -f ${REPMGRDCONF} \
node rejoin \
-d 'host=beta user=user_repmgr dbname=db_repmgr port=5432' \
--force-rewind

O nó alpha retorna ao cluster automaticamente, mas passa a ser um standby.

[$] [alpha] Verificando o cluster de replicação:

repmgr -f ${REPMGRDCONF} cluster show

ID | Name | Role | Status | Upstream | Location | Priority | Timeline | Connection string


----+-------------+---------+-----------+------------+----------+----------+----------+-----------------------
1 | alpha.local | standby | running | beta.local | default | 100 | 2 | host=alpha.local user=
2 | beta.local | primary | * running | | default | 100 | 2 | host=beta.local user=u
3 | gamma.local | standby | running | beta.local | default | 100 | 2 | host=gamma.local user=
4 | omega.local | witness | * running | beta.local | default | 0 | n/a | host=omega.local user=
418 15. Replicação

Switchover

[$] [alpha] Voltar a ser o servidor primário do cluster de replicação:

repmgr -f ${REPMGRDCONF} standby switchover

[$] [gamma] Faz o standby siga no novo standby:

repmgr -f ${REPMGRDCONF} standby follow --upstream-node-id=1

[$] [omega] Registrar o servidor como witness novamente para seguir o novo master:

repmgr -F \
-d db_repmgr \
-U user_repmgr \
-h alpha \
-f ${REPMGRDCONF} \
--verbose witness register

NOTICE: using provided configuration file "/etc/repmgr/13/repmgr.conf"


INFO: connecting to witness node "omega.local" (ID: 4)
INFO: connecting to primary node
INFO: "repmgr" extension is already installed
INFO: witness registration complete
NOTICE: witness node "omega.local" (ID: 4) successfully registered
16
Anexo 1 - Índices
• Sobre Índice
• Índices B-tree
• Índices Hash
• Índices GiST
• Índices SP-GiST
• Índices GIN
• Índices BRIN
• Índices Compostos
• Índices Parciais
• Índices de cobertura
• Reconstrução de Índices – REINDEX
• CLUSTER – Índices Clusterizados
• Excluindo um Índice

419
420 16. Anexo 1 - Índices

Sobre Índice
Índice (index ) é um recurso que agiliza buscas em tabelas.
Imagine que você está em uma biblioteca e gostaria de procurar “O Senhor dos Anéis”, de
Tolkien. O que seria mais fácil? Começar a vasculhar a biblioteca inteira até achar o livro
desejado (busca sequencial) ou buscar no arquivo da biblioteca (busca indexada), nas fichas
que estão ordenados por autor? Logicamente, se escolher buscar nas fichas, será muito mais
rápido, pois não será necessário vasculhar livro por livro na biblioteca, porque haverá uma
ficha do autor e daquele livro que mostrará exatamente onde está o livro desejado. É um
apontamento para a localização do livro. Um índice de banco de dados funciona de forma
semelhante, cujo funcionamento consiste em criar ponteiros para dados gravados em campos
específicos. Quando não existe índice em um campo usado como critério de filtragem, é feita
uma varredura em toda a tabela, de maneira a haver execuções de I/O desnecessárias, além
de também desperdiçar processamento.
Um índice é criado implicitamente para constraints UNIQUE e PRIMARY KEY.

Tipos de Índices

O PostgreSQL em seu core dispõe dos seguintes tipos de índices: Btree (padrão), Hash, GiST,
SP-GiST, GIN e BRIN, que serão vistos aqui.
Há também outros tipos de índices que são oferecidos como extensões.

Dicas Gerais

• Crie índices para campos cujas consultas o envolvam em uma das seguintes cláusulas:
WHERE, DISTINCT, ORDER BY, GROUP BY e LIKE;

• Crie índices para campos de chaves estrangeiras e em campos envolvidos como critérios
de junção (JOIN);

• Se houver uma consulta frequente utilize índices parciais com sua condição conforme a
consulta;

• Para consultas que buscam faixas de valores é bom ter um índice clusterizado para isso;

• O PostgreSQL oferece diferentes tipos de índices, o que nos dá a possibilidade de adotar


diferentes tipos de indexação conforme a consulta e seus tipos de dados, teste e adote
o mais adequado;

• Após criar um índice atualize as estatísticas da tabela com ANALYZE [1] ou VACCUM
ANALYZE [2];
421 16. Anexo 1 - Índices

• Antes de colocar uma base em produção faça testes com massas de dados considerável
e utilize o comando EXPLAIN ANALYZE [3] para verificar o plano de execução.

[1] https://www.postgresql.org/docs/current/static/sql-analyze.html
[2] https://www.postgresql.org/docs/current/static/sql-vacuum.html
[3] https://www.postgresql.org/docs/current/static/sql-explain.html
422 16. Anexo 1 - Índices

Índices B-tree
Pode lidar com consultas de igualdade ou de faixa de valores cujos dados podem ser ordenados.
O planejador de consultas do PostgreSQL considerará usar um índice B-tree sempre que um
campo indexado estiver envolvido em uma comparação usando um dos seguintes operadores:
<, <=, =, >=, > ou BETWEEN, IN, IS [NOT] NULL.
O otimizador pode também usar índices B-tree para consultas envolvendo operadores de padrão
de combinação LIKE e ~ se o padrão for uma constante e estiver no início da string, por exemplo:
coluna LIKE 'foo%' ou coluna ~ '^foo', mas não coluna LIKE '%bar'. Porém, se sua base não usa
locale C, será necessário criar o índice com uma classe de operador especial para suportar
indexação de consultas de casamento de padrões.
É possível também usar índices B-tree para ILIKE e ~*, mas apenas se o padrão começar com
caracteres não-alfabéticos, i. e., caracteres que não são afetados pela conversão upper/lower
case (letras maiúsculas/minúsculas).
Índices B-tree podem também ser usados para buscar dados ordenadamente. Isso não é sempre
mais rápido do que uma busca simples e ordenação, mas é sempre útil.

[>] Criação de tabela de teste:

SELECT
generate_series(1, 2000000)::int AS campo1, -- 2 milhões de registros
round((random()*10000))::int2 AS campo2,
round((random()*10000))::int2 AS campo3 INTO tb_foo;

[>] Verificando o plano de execução:

EXPLAIN ANALYZE
SELECT campo1
FROM tb_foo
WHERE campo2 BETWEEN 235 AND 587;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Gather (cost=1000.00..23350.62 rows=10000 width=4) (actual time=0.253..164.844 rows=70936 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Parallel Seq Scan on tb_foo (cost=0.00..21350.62 rows=4167 width=4) (actual time=0.949..140.846 rows=2
Filter: ((campo2 >= 235) AND (campo2 <= 587))
Rows Removed by Filter: 643021
Planning Time: 0.053 ms
Execution Time: 166.946 ms

A tabela aínda não tem índice, portanto, a busca vai ser sequencial.
423 16. Anexo 1 - Índices

[>] Criação de índice para a tabela:

CREATE INDEX idx_tb_foo_campo2 ON tb_foo (campo2);

Na criação do índice, não foi especificado seu tipo, portanto, esse é um índice btree, que é o
tipo padrão do PostgreSQL.

[>] Verificando o plano de execução:

EXPLAIN ANALYZE
SELECT campo1
FROM tb_foo
WHERE campo2 BETWEEN 235 AND 587;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on tb_foo (cost=138.93..9559.68 rows=10000 width=4) (actual time=5.573..42.467 rows=70936 l
Recheck Cond: ((campo2 >= 235) AND (campo2 <= 587))
Heap Blocks: exact=8848
-> Bitmap Index Scan on idx_tb_foo_campo2 (cost=0.00..136.43 rows=10000 width=0) (actual time=3.986..3.98
Index Cond: ((campo2 >= 235) AND (campo2 <= 587))
Planning Time: 0.230 ms
Execution Time: 44.851 ms

Comparando antes e depois da criação do índice, vemos que antes foi usada a busca sequencial
(Seq Scan) e levou 8.456 ms.
Após criar o índice, sendo que foi criado para o campo utilizado na condição do WHERE, vemos
que foi utilizada busca via índice (Bitmap Index Scan) e levou 44.851 ms para executar.
Por meio desse experimento, podemos ver o quão útil é ter um índice, devido à forma como
ele agiliza uma busca.
424 16. Anexo 1 - Índices

Índices Hash
Índices hash podem apenas lidar com comparações de igualdade. O planejador de consulta
considerará usar um índice hash sempre que uma coluna indexada estiver envolvida em uma
comparação com o operador =.
Até a versão 9.6 do PostgreSQL havia um aviso na documentação oficial desencorajando esse
tipo de índice, pois até então eles não eram escritos no WAL, não eram replicados e numa
eventual situação de recuperação de crash índices desse tipo deveriam ser reconstruídos com
uma reindexação (REINDEX). A partir da versão 10 do PostgreSQL, esse aviso desencorajando foi
removido. No entanto, devido à sua baixa amplitude de operadores e com o desenvolvimento
do tipo btree, seu uso ainda é questionável.

[>] Se a tabela existir, deve ser removida:

DROP TABLE IF EXISTS tb_foo;

[>] Criação de uma tabela de teste que tenha dez milhões de registros:

SELECT
generate_series(1, 10000000) AS numero
INTO tb_foo;

[>] Habilitar o cronômetro do psql:

\timing on

[>] Criação de dois índices, hash e btree, respectivamente:

CREATE INDEX idx_hash ON tb_foo USING hash (numero);


CREATE INDEX idx_btree ON tb_foo USING btree (numero);

Nota-se que a criação do índice hash demorou mais do que a do índice btree.
Foram criados dois índices para a mesma coluna da tabela, sendo um hash e o outro btree.
A intenção aqui é testar para validar se vale a pena criar um índice hash.
425 16. Anexo 1 - Índices

[>] Verificando o tamanho de cada índice criado:

SELECT
pg_size_pretty(pg_relation_size('idx_hash')) AS indice_hash,
pg_size_pretty(pg_relation_size('idx_btree')) AS indice_btree;

indice_hash | indice_btree
-------------+--------------
320 MB | 214 MB

Podemos notar que o índice btree é 33% menor do que o índice hash.

[>] Verificando do plano de execução de uma consulta de igualdade:

EXPLAIN ANALYZE
SELECT numero
FROM tb_foo
WHERE numero = 195773;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Index Only Scan using idx_btree on tb_foo (cost=0.43..4.45 rows=1 width=4) (actual time=0.571..0.572 rows=1
Index Cond: (numero = 195773)
Heap Fetches: 0
Planning Time: 7.447 ms
Execution Time: 1.072 ms

Aqui, vemos que o índice hash não foi utilizado, mas sim o btree.
Isso significa que vai ser sempre assim? A resposta é: depende.
Há versões do PostgreSQL que o índice hash pode ser proveitoso, o que não aconteceu nesse
teste.
426 16. Anexo 1 - Índices

Índices GiST
Índices GiST não são um único tipo de índice, mas sim uma infraestrutura interna que muitas
estratégias diferentes de indexação podem ser implementadas. Portanto, os operadores em
particular com que um índice GiST pode ser usado varia dependendo da estratégia de indexação
(a classe de operador). Como um exemplo, a distribuição padrão do PostgreSQL inclui classes
de operador GiST para vários tipos de dados geométricos de duas dimensões, que suporta
consultas indexadas usando os seguintes operadores: <<, &<, &>, >>, <<|, &<|, |&>, |>>, @>, <@,
~=, &&.
Muitas outras classes de operadores GiST estão disponíveis na coleção contrib ou como projetos
separados.

Índices GiST são também capazes de otimizar buscas “nearest-neighbor ” (vizinho mais próx-
imo) como:

SELECT * FROM lugares


ORDER BY localizacao <-> ponto '(101, 456)'
LIMIT 10;

Essa consulta encontra os dez lugares mais perto de um dado ponto alvo. A capacidade de
fazer isso é, novamente, dependente da classe de operador particular a ser usada.

Classes de operadores GiST built-in

Operadores de
Classe Tipo Operadores Ordenação
box_ops box &&, &>, &<, &<\|, >>, <<, <<\|, <@, @>, @,
\|&>, \|>>,~, ~=
cir- circle &&, &>, &<, &<\|, >>, <<, <<\|, <@, @>, @, <->
cle_ops \|&>, \|>>,~, ~=
inet_ops inet, cidr &&, >>, >>=, >, >=, <>, <<, <<=, <, <=, =
point_ops point >>, >^, <<, <@, <@, <@, <^, ~= <->
poly_ops polygon &&, &>, &<, &<\|, >>, <<, <<\|, <@, @>, @, <->
\|&>, \|>>,~, ~=
range_ops qualquer tipo &&, &>, &<, >>, <<, <@, -\|-, =, @>
de faixa
ts- tsquery <@, @>
query_ops
tsvec- tsvector @@
tor_ops
427 16. Anexo 1 - Índices

A classe inet_ops não é a classe padrão para tipos cidr e inet. Para utilizá-la, mencione o
nome da classe na criação do índice:

CREATE INDEX ON tabela USING GIST (campo_inet inet_ops);

[>] Criação de função para gerar um dado do tipo int4range:

CREATE OR REPLACE FUNCTION fc_gera_int4range(n int4)


RETURNS int4range AS $$
DECLARE
limite_superior int4 := round(random() * n);
limite_inferior int4 := round(random() * limite_superior);
BEGIN
RETURN ('['||limite_inferior||', '||limite_superior||']')::int4range;
END; $$ LANGUAGE PLPGSQL;

[>] Se a tabela existir, deve ser apagada:

DROP TABLE IF EXISTS tb_foo;

[>] Criação da tabela de teste:

CREATE TABLE tb_foo(


id int,
faixa int4range);

[>] Popular a tabela utilizando a função criada:

INSERT INTO tb_foo (id, faixa)


SELECT
generate_series(1, 1000000),
fc_gera_int4range(1000000);

[>] Criação de índices GiST sem declarar a classe de operador e declarando, respectivamente:

CREATE INDEX idx_gist ON tb_foo USING gist (faixa);


428 16. Anexo 1 - Índices

CREATE INDEX idx_gist_op_class ON tb_foo USING gist (faixa range_ops);

[>] Verificando o plano de execução:

EXPLAIN ANALYZE
SELECT id, faixa
FROM tb_foo WHERE faixa @> 777;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on tb_foo (cost=199.03..6536.91 rows=5000 width=36) (actual time=9.086..18.256 rows=5618 lo
Recheck Cond: (faixa @> 777)
Heap Blocks: exact=3695
-> Bitmap Index Scan on idx_gist_op_class (cost=0.00..197.78 rows=5000 width=0) (actual time=8.729..8.730
Index Cond: (faixa @> 777)
Planning Time: 3.417 ms
Execution Time: 18.810 ms

O planejador de consultas escolheu o índice cuja classe de operador foi declarada.


429 16. Anexo 1 - Índices

Índices SP-GiST
Como índices GiST, índices SP-GiST, oferecem uma infraestrutura que suporta vários tipos
de buscas. Permite implementar uma vasta faixa de diferentes estruturas de dados baseadas
em disco não balanceadas, como quadtrees, k-d trees e radix trees (tentativas).
Como um exemplo, a distribuição padrão do PostgreSQL inclui classes de operadores SP-GiST
para pontos de duas dimensões, que suportem consultas usando estes operadores: <<, >>, ~=,
<@, <^, >^.

Classes de operadores SP-GiST built-in

Classe Tipo Operadores


kd_point_ops point <<, <@, <^, >>, >^, ~=
quad_point_ops point <<, <@, <^, >>, >^, ~=
range_ops Qualquer tipo de faixa &&, &<, &>, -\|-, <<, <@, =, >>, @>
box_ops box <<, &<, &&, &>, >>, ~=, @>, <@, &<\|, <<\|, \|>>, \|&>
text_ops text <, <=, =, >, >=, ~<=~, ~<~, ~>=~, ~>~
inet_ops inet, cidr &&, >>, >>=, >, >=, <>, <<, <<=, <, <=, =

Das duas classes de operadores para o tipo de ponto, quad_point_ops é o padrão. kd_point_ops
suporta os mesmos operadores, mas usa uma estrutura diferente de dados de índice que pode
oferecer um melhor desempenho em algumas aplicações.

[>] Criação de índices SP-GiST, respectivamente, sem declarar e declarando classe de oper-
ador:

CREATE INDEX idx_spgist ON tb_foo USING spgist (faixa);


CREATE INDEX idx_spgist_op_class ON tb_foo USING spgist (faixa range_ops);
430 16. Anexo 1 - Índices

[>] Verificando o plano de execução:

EXPLAIN ANALYZE
SELECT id, faixa FROM tb_foo WHERE faixa @> 777;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on tb_foo (cost=175.11..6516.21 rows=5010 width=18) (actual time=2.543..25.354 rows=5497 lo
Recheck Cond: (faixa @> 777)
Heap Blocks: exact=3731
-> Bitmap Index Scan on idx_spgist_op_class (cost=0.00..173.86 rows=5010 width=0) (actual time=1.906..1.9
Index Cond: (faixa @> 777)
Planning Time: 0.314 ms
Execution Time: 25.596 ms

Como aconteceu com os índices GiST, aqui também o planejador de consultas escolheu o
índice cuja classe de operador foi declarada.

[>] Verificando o tamanho de todos os índices da tabela:

SELECT
indexname indice,
pg_size_pretty(pg_relation_size(indexname::text)) tamanho
FROM pg_indexes
WHERE tablename = 'tb_foo';

indice | tamanho
---------------------+---------
idx_gist | 62 MB
idx_gist_op_class | 61 MB
idx_spgist | 52 MB
idx_spgist_op_class | 52 MB

Os índices SP-GiST são menores do que os índices GiST, e o planejador de consultas utilizou o
índice SP-GiST de classe de operador declarada. Nesse caso, foi a melhor escolha de indexação
de acordo com a consulta.

[>] Remover a tabela de teste:

DROP TABLE tb_foo;


431 16. Anexo 1 - Índices

Índices GIN
Índices GIN são “índices invertidos”, apropriados para valores de dados que contêm valores
de componentes múltiplos, como arrays. Um índice invertido contém uma entrada para cada
valor componente e pode eficientemente lidar com consultas que testam a presença de valores
em componentes específicos.
Como GiST e SP-GiST, GIN pode suportar muitas diferentes estratégias de indexação definidas
por usuário, e os operadores com que um índice GIN podem ser usados variam de acordo com
a estratégia de indexação.
Como exemplo, a distribuição padrão de PostgreSQL inclui uma classe de operador GIN para
arrays, que suporta consultas indexadas usando estes operadores: <@, @>, =, &&.
Muitos outros operadores de classes GIN estão disponíveis na coleção contrib como projetos
separados.

Classes de Operadores GIN Built-in

Classe Tipo Operadores


kd_point_ops Qualquer array &&, <@, =, @>
quad_point_ops point ?, ?&, ?\|, @>
range_ops Qualquer tipo de faixa @>
box_ops box @@, @@@

[>] Criação da tabela de teste:

CREATE TABLE tb_foo (


id int,
vetor int[]);

[>] Popular a tabela:

INSERT INTO tb_foo (id, vetor)


SELECT
generate_series(1, 1000000),
ARRAY[
round(random() * 10000000),
round(random() * 10000000),
round(random() * 10000000)];
432 16. Anexo 1 - Índices

[>] Criação dos índices sem e com declaração de classe de operador, respectivamente:

CREATE INDEX idx_gin ON tb_foo USING gin (vetor);


CREATE INDEX idx_gin_op_class ON tb_foo USING gin (vetor array_ops);

[>] Verificando o plano de execução:

EXPLAIN ANALYZE
SELECT vetor FROM tb_foo WHERE vetor @> ARRAY[754532];

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on tb_foo (cost=62.75..7672.14 rows=5000 width=33) (actual time=0.995..1.011 rows=2 loops=1
Recheck Cond: (vetor @> '{754532}'::integer[])
Heap Blocks: exact=2
-> Bitmap Index Scan on idx_gin_op_class (cost=0.00..61.50 rows=5000 width=0) (actual time=0.978..0.979 r
Index Cond: (vetor @> '{754532}'::integer[])
Planning Time: 2.249 ms
Execution Time: 1.043 ms

O índice com classe de operador declarada foi escolhido pelo planejador de consultas.

[>] Remover a tabela de teste:

DROP TABLE tb_foo;


433 16. Anexo 1 - Índices

Índices BRIN
Índices BRIN (uma abreviação para Block Range INdexes) armazena sumários sobre os valores
armazenados em faixas de blocos físicos consecutivos de uma tabela.
Assim como GiST, SP-GiST e GIN, BRIN pode suportar muitas diferentes estratégias e oper-
adores com que um índice BRIN pode ser usado - o que varia de acordo com a estratégia de
indexação.
Para tipos de dados que tem uma ordem de classificação linear, os dados indexados corre-
spondem ao mínimo e máximo de valores na coluna para cada faixa de bloco. Isso suporta
consultas indexadas usando estes operadores: <, <=, =, >=, >.
Um índice BRIN para uma busca de uma consulta é uma mistura de busca sequencial e busca
indexada porque o que essa busca indexada está armazenando é uma faixa de dados dado um
número fixo de blocos de dados.

[>] Criação de tabela de testes:

CREATE TABLE tb_temperatura_log (


dt timestamp without time zone,
temperatura int);

[>] Popular tabela:

INSERT INTO tb_temperatura_log (dt, temperatura) VALUES


(generate_series(
'2017-01-01'::timestamp,
'2018-03-31'::timestamp,
'1 second'),
round(random() * 100)::int);

[>] Habilitar o cronômetro do psql:

\timing on

[>] Criação de índice btree:

CREATE INDEX idx_btree ON tb_temperatura_log USING btree (dt);

Time: 56463.054 ms (00:56.463)


434 16. Anexo 1 - Índices

Classe de operadores BRIN built-in

Classe Tipo Operadores


abstime_minmax_ops abstime <, <=, =, >=, >
int8_minmax_ops bigint <, <=, =, >=, >
bit_minmax_ops bit <, <=, =, >=, >
varbit_minmax_ops bit varying <, <=, =, >=, >
box_inclusion_ops box <<, &<, &&, &>, >>, ~=, @>,
<@, &<\|, <<\|, \|>>, \|&>
bytea_minmax_ops bytea <, <=, =, >=, >
bpchar_minmax_ops character <, <=, =, >=, >
char_minmax_ops “char” <, <=, =, >=, >
date_minmax_ops date <, <=, =, >=, >
float8_minmax_ops double precision <, <=, =, >=, >
inet_minmax_ops inet <, <=, =, >=, >
network_inclusion_ops inet &&, >>=, <<=, =, >>, <<
int4_minmax_ops integer <, <=, =, >=, >
interval_minmax_ops interval <, <=, =, >=, >
macaddr_minmax_ops macaddr <, <=, =, >=, >
macaddr8_minmax_ops macaddr8 <, <=, =, >=, >
name_minmax_ops name <, <=, =, >=, >
numeric_minmax_ops numeric <, <=, =, >=, >
pg_lsn_minmax_ops pg_lsn <, <=, =, >=, >
oid_minmax_ops oid <, <=, =, >=, >
range_inclusion_ops any range type <<, &<, &&, &>, >>, @>, <@,
-\|-, =, <, <=, =, >, >=
float4_minmax_ops real <, <=, =, >=, >
reltime_minmax_ops reltime <, <=, =, >=, >
int2_minmax_ops smallint
text_minmax_ops text <, <=, =, >=, >
tid_minmax_ops tid <, <=, =, >=, >
timestamp_minmax_ops timestamp without time zone <, <=, =, >=, >
timestamptz_minmax_ops timestamp with time zone <, <=, =, >=, >
time_minmax_ops time without time zone <, <=, =, >=, >
timetz_minmax_ops time with time zone <, <=, =, >=, >
uuid_minmax_ops uuid <, <=, =, >=, >
435 16. Anexo 1 - Índices

[>] Criação de índice BRIN sem classe de operador:

CREATE INDEX idx_brin ON tb_temperatura_log USING brin (dt);

Time: 9595.854 ms (00:09.596)

É de se notar que a criação do índice BRIN é muito mais rápida do que a do índice B-tree.

[>] Criação de índice BRIN com classe de operador:

CREATE INDEX idx_brin_op_class ON tb_temperatura_log


USING brin (dt timestamp_minmax_ops);

Páginas por Faixa

Em índices BRIN, exist um parâmetro que controla a quantidade de páginas por faixa,
pages_per_range, cujo valor padrão é 128.
Quanto mais páginas por faixa, menor o índice será. No entanto, a parte de busca sequencial
será maior.

[>] Criação de índices com diferentes configurações para páginas por faixa:

CREATE INDEX idx_brin_op_class_64 ON tb_temperatura_log


USING brin (dt timestamp_minmax_ops)
WITH (pages_per_range = 64);

CREATE INDEX idx_brin_op_class_256 ON tb_temperatura_log


USING brin (dt timestamp_minmax_ops)
WITH (pages_per_range = 256);

CREATE INDEX idx_brin_op_class_512 ON tb_temperatura_log


USING brin (dt timestamp_minmax_ops)
WITH (pages_per_range = 512);
436 16. Anexo 1 - Índices

[>] Verificando o tamanho dos índices na tabela de teste:

SELECT
indexname indice,
pg_size_pretty(pg_relation_size(indexname::text)) tamanho
FROM pg_indexes
WHERE tablename = 'tb_temperatura_log'
ORDER BY pg_relation_size(indexname::text);

indice | tamanho
-----------------------+---------
idx_brin_op_class_512 | 32 kB
idx_brin_op_class_256 | 40 kB
idx_brin | 72 kB
idx_brin_op_class | 72 kB
idx_brin_op_class_64 | 128 kB
idx_btree | 840 MB

[>] Remover a tabela de teste:

DROP TABLE tb_temperatura_log;


437 16. Anexo 1 - Índices

Índices Compostos
São aqueles que contêm em sua composição mais de um campo.

Sintaxe:

CREATE INDEX nome_do_index


ON nome_da_tabela (campoX, campoY, campoZ);

[>] Criação da tabela de teste:

CREATE TABLE tb_foo(


id_ int PRIMARY KEY
GENERATED BY DEFAULT AS IDENTITY,
campo2 int,
campo3 int);

[>] Populando a tabela:

INSERT INTO tb_foo (id_, campo2, campo3)


SELECT
generate_series(1, 1000000),
(random() * 1000000)::int,
(random() * 1000000)::int;

[>] Criação de índice composto:

CREATE INDEX idx_tb_index_campo2_campo3 ON tb_foo (campo2, campo3);


438 16. Anexo 1 - Índices

[>] Verificar o plano de execução:

EXPLAIN ANALYZE
SELECT id_
FROM tb_foo
WHERE (campo2 BETWEEN 235 AND 587)
AND campo3 = 1000;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Index Scan using idx_tb_index_campo2_campo3 on tb_foo (cost=0.42..12.72 rows=1 width=4) (actual time=0.032..
Index Cond: ((campo2 >= 235) AND (campo2 <= 587) AND (campo3 = 1000))
Planning Time: 0.354 ms
Execution Time: 0.047 ms

[>] Remover a tabela de teste:

DROP TABLE tb_foo;


439 16. Anexo 1 - Índices

Índices Parciais
Índice parcial é aquele que aponta para registros de acordo com uma condição.

Sintaxe:

CREATE INDEX nome_do_index


ON nome_da_tabela (campo)
WHERE condição;

[>] Criação da tabela de teste:

CREATE TABLE tb_foo(campo1 int);

[>] Popular tabela:

INSERT INTO tb_foo SELECT generate_series(1, 1000000);

[>] Análise sem índices de Valores múltiplos de 19:

EXPLAIN ANALYZE
SELECT * FROM tb_foo WHERE campo1 % 19 = 0;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Gather (cost=1000.00..12175.00 rows=5000 width=4) (actual time=0.203..69.548 rows=52631 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Parallel Seq Scan on tb_foo (cost=0.00..10675.00 rows=2083 width=4) (actual time=0.010..32.741 rows=17
Filter: ((campo1 % 19) = 0)
Rows Removed by Filter: 315790
Planning Time: 0.093 ms
Execution Time: 71.113 ms

Por não ter índices, foi usada uma busca sequencial (Seq Scan).

[>] Criação de índice total:

CREATE INDEX idx_total ON tb_foo (campo1);


440 16. Anexo 1 - Índices
441 16. Anexo 1 - Índices

[>] Análise sem índices de valores múltiplos de 19:

EXPLAIN ANALYZE
SELECT * FROM tb_foo WHERE campo1 % 19 = 0;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Gather (cost=1000.00..12175.00 rows=5000 width=4) (actual time=0.203..69.548 rows=52631 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Parallel Seq Scan on tb_foo (cost=0.00..10675.00 rows=2083 width=4) (actual time=0.010..32.741 rows=17
Filter: ((campo1 % 19) = 0)
Rows Removed by Filter: 315790
Planning Time: 0.093 ms
Execution Time: 71.113 ms

Mesmo com o índice criado nessa consulta, não será utilizado.

[>] Criação de índice parcial múltiplos de 19:

CREATE INDEX idx_19 ON tb_foo (campo1) WHERE campo1 % 19 = 0;

[>] Análise sem índices de valores múltiplos de 19:

EXPLAIN ANALYZE
SELECT * FROM tb_foo WHERE campo1 % 19 = 0;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Index Only Scan using idx_19 on tb_foo (cost=0.29..131.29 rows=5000 width=4) (actual time=0.034..7.213 rows=
Heap Fetches: 0
Planning Time: 0.233 ms
Execution Time: 10.035 ms

A condição do índice parcial combinou com a consulta feita. Então, o índice foi utilizado.
442 16. Anexo 1 - Índices

[>] Análise com uma consulta de condição diferente de números divíveis por 19:

EXPLAIN ANALYZE
SELECT * FROM tb_foo
WHERE campo1 BETWEEN 241 AND 875;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Index Only Scan using idx_total on tb_foo (cost=0.42..21.14 rows=636 width=4) (actual time=0.012..0.106 rows
Index Cond: ((campo1 >= 241) AND (campo1 <= 875))
Heap Fetches: 0
Planning Time: 0.306 ms
Execution Time: 0.150 ms

Com uma consulta mais abrangente foi utilizado o outro índice.

[>] Verificando o tamanho dos índices na tabela de teste:

SELECT
indexname indice,
pg_size_pretty(pg_relation_size(indexname::text)) tamanho
FROM pg_indexes
WHERE tablename = 'tb_foo'
ORDER BY pg_relation_size(indexname::text);

indice | tamanho
-----------+---------
idx_19 | 1168 kB
idx_total | 21 MB

[>] Remover a tabela de teste:

DROP TABLE tb_foo;

Conclusão

Contatou-se o que foi dito na teoria: antes da criação dos índices, a primeira busca foi
sequencial.
Após a criação dos índices, o planejador de consultas já podia contar com eles, optando por
usar o índice com maior restrição de valores. Isso leva a um tempo menor, usufruindo de uma
busca agora indexada por um índice parcial.
443 16. Anexo 1 - Índices

Índices de cobertura
Também conhecidos pelo termo em inglês covering indexes, permitem que sejam feitas buscas
apenas em índices (index-only scans) se a listagem da consulta combinar com as colunas
incluídas no índice. Para isso, foi adotada a cláusula INCLUDE. A cláusula INCLUDE especifica
uma lista de colunas que serão incluídas no índice como colunas não-chave.

Sintaxe:

CREATE INDEX idx_teste ON x (coluna_1, coluna_2) INCLUDE (coluna_3);

[>] Criação de tabela de teste:

CREATE TABLE tb_foo(


id_ int PRIMARY KEY
GENERATED BY DEFAULT AS IDENTITY,
numero int,
texto text);

[>] Popular a tabela com dois milhões de registros:

INSERT INTO tb_foo (numero, texto)


SELECT (random() * 1000000)::int,
substr(md5(generate_series(1, 2000000)::text), 1, 7);

[>] Criação de um índice composto:

CREATE INDEX idx_teste ON tb_foo (numero, id_);


444 16. Anexo 1 - Índices

[>] Verificando o plano de execução selecionando apenas colunas do índice:

EXPLAIN ANALYZE
SELECT
numero, id_
FROM tb_foo
WHERE numero > 25000
ORDER BY numero, id_ LIMIT 20;

QUERY PLAN
--------------------------------------------------------------------------------------------
Limit (cost=0.43..1.00 rows=20 width=8) (actual time=0.080..0.087 rows=20 loops=1)
-> Index Only Scan using idx_teste on tb_foo (cost=0.43..55555.52 rows=1951034 width=8) . . .
Index Cond: (numero > 25000)
Heap Fetches: 0
Planning Time: 0.351 ms
Execution Time: 0.107 ms

[>] Plano de execução selecionando coluna que não está no índice:

EXPLAIN ANALYZE
SELECT
numero, id_, texto
FROM tb_foo
WHERE numero > 25000
ORDER BY numero, id_ LIMIT 20;

QUERY PLAN
--------------------------------------------------------------------------------------------
Limit (cost=0.43..1.44 rows=20 width=16) (actual time=0.025..0.061 rows=20 loops=1)
-> Index Scan using idx_teste on tb_foo (cost=0.43..98799.48 rows=1951034 width=16) (ac . . .
Index Cond: (numero > 25000)
Planning Time: 0.199 ms
Execution Time: 0.081 ms

Em ambos os planos de execução, o índice criado foi utilizado.

[>] Criação de um segundo índice composto que tem três colunas:

CREATE INDEX idx_teste2 on tb_foo (numero, id_, texto);


445 16. Anexo 1 - Índices

[>] Verificando o plano de execução selecionando os três campos da tabela:

EXPLAIN ANALYZE
SELECT
numero, id_, texto
FROM tb_foo
WHERE numero > 25000
ORDER BY numero, id_ LIMIT 20;

QUERY PLAN
--------------------------------------------------------------------------------------------
Limit (cost=0.43..1.09 rows=20 width=16) (actual time=0.042..0.048 rows=20 loops=1)
-> Index Only Scan using idx_teste2 on tb_foo (cost=0.43..64159.52 rows=1951034 width=1 . . .
Index Cond: (numero > 25000)
Heap Fetches: 0
Planning Time: 0.273 ms
Execution Time: 0.065 ms

Foi utilizado o segundo índice criado, que tem as três colunas em sua composição.

[>] Criação de índice de cobertura onde o campo “texto” é o campo incluído na cobertura:

CREATE INDEX idx_include ON tb_foo (numero, id_) INCLUDE (texto);

[>] Plano de execução com uma consulta que lista os três campos:

EXPLAIN ANALYZE
SELECT
numero, id_, texto
FROM tb_foo
WHERE numero > 25000
ORDER BY numero, id_ LIMIT 20;

QUERY PLAN
--------------------------------------------------------------------------------------------
Limit (cost=0.43..1.09 rows=20 width=16) (actual time=0.042..0.047 rows=20 loops=1)
-> Index Only Scan using idx_include on tb_foo (cost=0.43..64159.52 rows=1951034 width= . . .
Index Cond: (numero > 25000)
Heap Fetches: 0
Planning Time: 0.233 ms
Execution Time: 0.064 ms
446 16. Anexo 1 - Índices

[>] Tamanho dos índices na tabela de teste:

SELECT
indexname indice,
pg_size_pretty(pg_relation_size(indexname::text)) tamanho
FROM pg_indexes
WHERE tablename = 'tb_foo'
ORDER BY pg_relation_size(indexname::text);

indice | tamanho
-------------+---------
tb_foo_pkey | 43 MB
idx_teste | 43 MB
idx_teste2 | 60 MB
idx_include | 60 MB

[>] Remover a tabela de teste:

DROP TABLE tb_foo;


447 16. Anexo 1 - Índices

Reconstrução de índices: REINDEX


O comando REINDEX faz a reconstrução de um índice utilizando os dados guardados em sua
tabela e substituindo sua cópia antiga.
É utilizado nas seguintes situações:

• Um índice que acabou ficando “inchado”, ou seja, que contém muitas páginas vazias
ou quase vazias. Isso pode ocorrer com índices B-tree sob certos padrões incomuns
de acesso. REINDEX fornece uma maneira de reduzir o consumo de espaço do índice,
escrevendo uma nova versão sem as páginas mortas;

• Se for alterado algum parâmetro de armazenamento para um índice (como fillfactor,


por exemplo), e desejar assegurar que a mudança tenha o efeito completo;

• Um índice feito com a opção CONCURRENTLY falhou, deixando um índice “inválido”.


A partir da versão 12 do PostgreSQL, há a opção de se fazer reindexação sem afetar a
produção (com trava), utilizando a reindexação concorrente (REINDEX CONCURRENTLY).

Sintaxe:

REINDEX { INDEX | TABLE | SCHEMA | DATABASE | SYSTEM } [ CONCURRENTLY ] nome

Onde:

INDEX: índice específico;


TABLE: todos os índices da tabela específica;
SCHEMA: todos os índices de um schema específico;
DATABASE: todos os índices do banco de dados específico;
SYSTEM: todos os índices em todos os catálogos no âmbito do atual banco de Dados. Índices
em tabelas de usuários não são processados;
CONCURRENTLY: faz a reconstrução de índices sem usar travas que previnem modificações con-
correntes na tabela.
448 16. Anexo 1 - Índices

[>] Criação de tabela de teste:

CREATE TABLE tb_foo (campo int);

[>] Criação de índice para a tabela:

CREATE INDEX idx_teste ON tb_foo (campo);

[>] Popular a tabela:

INSERT INTO tb_foo (campo) SELECT generate_series(1, 1000000);

[>] Alterando o parâmetro de armazenamento fillfactor:

ALTER INDEX idx_teste SET (fillfactor = 70);

[>] Reconstruindo o índice:

REINDEX INDEX idx_teste;

[>] Remover a tabela de teste:

DROP TABLE tb_foo;


449 16. Anexo 1 - Índices

CLUSTER – Índices clusterizados


O comando CLUSTER agrupa uma tabela de acordo com um índice de modo a aumentar a per-
formance no banco de dados.
A tabela é fisicamente reordenada, baseando-se na informação do índice.
O agrupamento é feito somente uma vez: após ser atualizada, as atualizações feitas nas lin-
has da tabela não seguirão o agrupamento, ou seja, não será feita nenhuma tentativa para
armazenar as linhas novas ou atualizadas na ordem do índice.
Se for desejado, a tabela pode ser reagrupada periodicamente, executando o comando nova-
mente.
Sem nenhum parâmetro, executa em todas tabelas que já foram agrupadas anteriormente no
banco de dados atual.
Sintaxe:

CLUSTER [VERBOSE] tabela [ USING index_name ]


CLUSTER [VERBOSE]

[>] Criação de tabela de teste:

CREATE TEMP TABLE tb_foo(


id_ int PRIMARY KEY
GENERATED BY DEFAULT AS IDENTITY,
cor text);

[>] Inserindo registros:

INSERT INTO tb_foo (cor) VALUES


('Laranja'),
('Preto'),
('Branco'),
('Azul'),
('Amarelo'),
('Vermelho'),
('Verde'),
('Cinza');
450 16. Anexo 1 - Índices

[>] Verificando os registros da tabela:

TABLE tb_foo;

id_ | cor
-----+----------
1 | Laranja
2 | Preto
3 | Branco
4 | Azul
5 | Amarelo
6 | Vermelho
7 | Verde
8 | Cinza

[>] Atualização:

UPDATE tb_foo SET cor = 'Vinho' WHERE id_ = 6;

[>] Verificando os registros da tabela:

TABLE tb_foo;

id_ | cor
-----+---------
1 | Laranja
2 | Preto
3 | Branco
4 | Azul
5 | Amarelo
7 | Verde
8 | Cinza
6 | Vinho

Nota-se que o registro atualizado foi parar no final da tabela.


451 16. Anexo 1 - Índices

[>] Verificando na estrutura da tabela o nome do índice para clusterização:

\d tb_foo

Table "pg_temp_3.tb_foo"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+----------------------------------
id_ | integer | | not null | generated by default as identity
cor | text | | |
Indexes:
"tb_foo_pkey" PRIMARY KEY, btree (id_)

Como a tabela já foi criada com uma chave primária, automaticamente essa chave primária
terá um índice atrelado a ela: tb_foo_pkey.

[>] Clusterização da tabela com base em um determinado índice:

CLUSTER tb_foo USING tb_foo_pkey;

[>] Verificando os registros da tabela:

TABLE tb_foo;

id_ | cor
-----+---------
1 | Laranja
2 | Preto
3 | Branco
4 | Azul
5 | Amarelo
6 | Vinho
7 | Verde
8 | Cinza

Agora, os registros foram agrupados ordenando-se pelo seu índice, cujo campo é o id_.

[>] Remover a tabela de teste:

DROP TABLE tb_foo;


452 16. Anexo 1 - Índices

Exclusão de índices
Não remova um índice de seu banco sem antes saber o quão útil ele é.
A visão (view ) de sistema pg_stat_user_indexes é extremamente útil para essa decisão de excluir
um índice, pois ela traz dados de índices criados por usuários, informando o quanto cada um
foi usado em diferentes aspectos.

[>] Criação de tabela de teste:

CREATE TEMP TABLE tb_foo(


id_ int PRIMARY KEY
GENERATED BY DEFAULT AS IDENTITY,
campo1 int,
campo2 text);

[>] Criação de índices:

CREATE INDEX idx_1 ON tb_foo (campo1);


CREATE INDEX idx_2 ON tb_foo (campo2);

[>] Popular a tabela:

INSERT INTO tb_foo (id_, campo1, campo2)


SELECT
generate_series(1, 1000000),
(random() * 10000)::int,
md5((random() * 10000)::text);

[>] Plano de execução de uma consulta efetivamente executada usando como critério o id_:

EXPLAIN ANALYZE
SELECT campo2 FROM tb_foo
WHERE id_ BETWEEN 5 AND 20000;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on tb_foo (cost=113.91..8661.20 rows=5608 width=32) (actual time=1.687..6.697 rows=19996 lo
Recheck Cond: ((id_ >= 5) AND (id_ <= 20000))
Heap Blocks: exact=187
-> Bitmap Index Scan on tb_foo_pkey (cost=0.00..112.51 rows=5608 width=0) (actual time=1.640..1.640 rows=
Index Cond: ((id_ >= 5) AND (id_ <= 20000))
Planning Time: 0.112 ms
Execution Time: 7.643 ms
453 16. Anexo 1 - Índices

[>] Plano de execução de uma consulta efetivamente executada usando como critério o
campo1:

EXPLAIN ANALYZE
SELECT campo2 FROM tb_foo
WHERE campo1 BETWEEN 5 AND 20000;

QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on tb_foo (cost=81.91..8629.20 rows=5608 width=32) (actual time=38.723..208.996 rows=999584
Recheck Cond: ((campo1 >= 5) AND (campo1 <= 20000))
Heap Blocks: exact=9346
-> Bitmap Index Scan on idx_1 (cost=0.00..80.51 rows=5608 width=0) (actual time=37.848..37.848 rows=99958
Index Cond: ((campo1 >= 5) AND (campo1 <= 20000))
Planning Time: 0.079 ms
Execution Time: 236.472 ms

[>] Estatísticas de uso de índices criados por usuários:

SELECT
indexrelname, relname, idx_scan, idx_tup_read, idx_tup_fetch
FROM pg_stat_user_indexes;

indexrelname | relname | idx_scan | idx_tup_read | idx_tup_fetch


--------------+---------+----------+--------------+---------------
tb_foo_pkey | tb_foo | 1 | 19996 | 0
idx_1 | tb_foo | 1 | 999584 | 0
idx_2 | tb_foo | 0 | 0 | 0

indexrelname: Nome do índice;


relname: Nome da tabela à qual o índice pertence;
idx_scan: Quantas vezes o índice foi usado;
idx_tup_read: Quantas tuplas o índice leu;
idx_tup_fetch: Quantas tuplas o índice recuperou.

Nota-se que em momento nenhum o índice idx_2 foi utilizado.


Supondo que assim permanecerá, pois os tipos de consultas não mudarão e fatalmente não
utilizarão esse índice, então é melhor apagá-lo, pois além de consumir mais espaço em disco,
cada INSERT ou UPDATE será mais oneroso.

[>] Exclusão de Índice:

DROP INDEX idx_2;


17
Anexo 2 - Scripts de conveniência
• O que são scripts de conveniência?

454
455 17. Anexo 2 - Scripts de conveniência

O que são scripts de conveniência?


Ao instalar o PostgreSQL via pacote, dependendo da distribuição, podem haver scripts adi-
cionais para fins de gerenciamento de instâncias (clusters) separando-as por versões (ma-
joritárias do PostgreSQL) e um nome. Esses scripts chamamos de scripts de conveniência.

Scripts de conveniência Debian

Em distribuições Linux da família Debian (Debian, Ubuntu, Linux Mint, etc), os scripts de
conveniência são fornecidos pelo pacote postgresql-common.
Maiores detalhes sobre cada um dos scripts que compõem estão disponíveis em:

https://manpages.debian.org/stretch/postgresql-common/index.html

[#] Listando as instâncias (clusters):

pg_lsclusters

Ver Cluster Port Status Owner Data directory Log file


13 main 5432 online postgres /var/lib/postgresql/13/main /var/log/postgresql/postgresql
-13-main.log

O script pg_lsclusters lista as instâncias presentes no servidor.


Sua saída, respectivamente, tem as colunas de versão, nome da instância, porta de escuta do
serviço, estado, usuário proprietário, diretório de dados (PGDATA) e arquivo de log.

[#] Criar variável de ambiente para a versão majoritária do PostgreSQL:

read -p 'Digite a versão majoritária do PostgreSQL: ' PGMAJOR


456 17. Anexo 2 - Scripts de conveniência

[#] Alternativamente criar uma nova instância:

pg_createcluster ${PGMAJOR} foo -- -k \


--auth-local=trust \
--auth-host=scram-sha-256

Creating new PostgreSQL cluster 13/foo ...


/usr/lib/postgresql/13/bin/initdb -D /var/lib/postgresql/13/foo -k --auth-local=trust --auth-host=scram-
sha-256
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.

The database cluster will be initialized with locale "en_US.UTF-8".


The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".
Data page checksums are enabled.
fixing permissions on existing directory /var/lib/postgresql/13/foo ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting default time zone ... America/Sao_Paulo
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok
Success. You can now start the database server using:
pg_ctlcluster 13 foo start
Ver Cluster Port Status Owner Data directory Log file
13 foo 5433 down postgres /var/lib/postgresql/13/foo /var/log/postgresql/postgresql-13-foo.log

Foi criado um novo cluster (instância) cujo nome é “foo” e, após os dois hífens, foram passadas
opções do initdb para usar checksums para páginas de dados, autenticação local sem senha e
autenticação TCP/IP senhas criptografadas com hash scram-sha-256.

[#] Listando novamente as instâncias (clusters):

pg_lsclusters

Ver Cluster Port Status Owner Data directory Log file


13 foo 5433 down postgres /var/lib/postgresql/13/foo /var/log/postgresql/postgresql-13-
foo.log
13 main 5432 online postgres /var/lib/postgresql/13/main /var/log/postgresql/postgresql-13-
main.log

Nota-se que à nova instância criada já foi atribuído um número de porta diferente para evitar
conflito com a main, que é a padrão e já existia.
No entanto, essa instância ainda não está rodando (down).
457 17. Anexo 2 - Scripts de conveniência

[#] Iniciando a nova instância:

pg_ctlcluster ${PGMAJOR} foo start

[#] Listando as instâncias após o start:

pg_lsclusters

Ver Cluster Port Status Owner Data directory Log file


13 foo 5433 online postgres /var/lib/postgresql/13/foo /var/log/postgresql/postgresql-13-
foo.log
13 main 5432 online postgres /var/lib/postgresql/13/main /var/log/postgresql/postgresql-13-
main.log

Agora ambos os clusters estão rodando, cada um com sua própria porta.

[#] Removendo o cluster criado:

pg_dropcluster --stop ${PGMAJOR} foo

Para remover uma instância, é necessário que a ela esteja parada, então a opção --stop faz
com que a remoção seja possível.

[#] Listando as instâncias após o pg_dropcluster:

pg_lsclusters

Ver Cluster Port Status Owner Data directory Log file


13 main 5432 online postgres /var/lib/postgresql/13/main /var/log/postgresql/postgresql-13-
main.log

Permaneceu a instância original.


458 17. Anexo 2 - Scripts de conveniência

Scripts de conveniência RedHat

A quantidade de scripts exclusivos de distribuições da família RedHat (Fedora, CentOS, Al-


maLinux, etc) é bem reduzida com relação ao Debian.
Temos:

• /usr/pgsql-<VERSÃO MAJORITÁRIA>/bin/postgresql-<VERSÃO MAJORITÁRIA>-check-db-dir


Sua única função é verificar o diretório de dados (PGDATA), ou seja, se o mesmo foi
inicializado corretamente. Se sim, nenhuma mensagem é emitida. Caso contrário uma
mensagem de erro é dada.

• /usr/pgsql-<VERSÃO MAJORITÁRIA>/bin/postgresql-<VERSÃO MAJORITÁRIA>-setup


Acumula as funções de inicializar um diretório de dados (com initdb) e também fazer
upgrade de versão (utilizando o pg_upgrade).
Esse script também se faz necessário após a instalação do PostgreSQL via pacote para
inicializar o PGDATA, pois não é feito automaticamente, diferente de como é no Debian.
18
Anexo 3 - Estratégias de
atualização
• Sobre estratégias de atualização
• pg_upgrade

459
460 18. Anexo 3 - Estratégias de atualização

Sobre estratégias de atualização


Para usufruir de novos recursos oferecidos pelo PostgreSQL, é necessário fazer a atualização
de sua versão majoritária. Em uma versão minoritária, são apenas correções de bugs ou falhas
de segurança.
Para fazer a atualização do PostgreSQL, há algumas estratégias, cada uma com suas vantagens
e desvantagens.
O termo migração também pode ser utilizado para esse fim, que segundo a definição de
dicionários é o ato de se mudar de um lugar para outro. Nesse contexto, migrar de uma versão
para outra.

Atualização via dump

Processo razoavelmente simples em que é feito um dump da base de dados ou mesmo de toda
instância de uma versão mais antiga e restaurado em uma instância com uma versão mais
nova.
Temos a vantagem de na restauração não ter bloats (inchaços) nas tabelas, mas é um processo
muito demorado, pois cada comando (SQL) tem que ser processado individualmente.

Atualização via replicação lógica

Interessante método com um downtime (tempo de parada) praticamente zero.


No cluster de replicação, temos o publicador de versão mais antiga e o assinante de versão
mais nova.
Assim que o assinante estiver devidamente sincronizado, é possível fazer a “virada de chave”
na aplicação, apontando para o novo servidor de banco de dados.
Como desvantagem, podemos dizer que há uma certa complexidade para configurar o cluster
de replicação e também as limitações da replicação lógica.

pg_upgrade

Antes chamado pg_migrator, possibilita a migração de versões majoritárias do PostgreSQL sem


utilizar dumps. O pg_upgrade suporta atualizações desde a série 8.3.X, incluindo snapshots
e lançamentos alpha.
461 18. Anexo 3 - Estratégias de atualização

Aviso

As instâncias devem combinar entre si a respeito de checksum (opção -k do initdb, checksum


de páginas), ou seja, ou ambos têm ou ambos não têm.

Parâmetros e variáveis de ambiente do pg_upgrade

• -b / --old-bindir / PGBINOLD: binários antigos;


• -B / --new-bindir / PGBINNEW: binários novos;
• -d / --old-datadir / PGDATAOLD: diretório de dados antigo;
• -D / --new-datadir / PGDATANEW: diretório de dados novo.

Sobre o laboratório

Na mesma máquina serão instaladas duas versões do PostgreSQL.


A padronização de diretórios faz muita diferença na hora de fazer uma migração, agilizando
muito o trabalho.

PostgreSQL instalado via compilação de código-fonte

Instância antiga

Versão ${PGMAJOR_OLD}
Porta 5432
PGDATA /var/local/pgsql/${PGMAJOR_OLD}/data
Binários /usr/local/pgsql/${PGMAJOR_OLD}/bin

Instância nova

Versão ${PGMAJOR_NEW}
Porta 5433 (provisória)
PGDATA /var/local/pgsql/${PGMAJOR_NEW}/data
Binários /usr/local/pgsql/${PGMAJOR_NEW}/bin

[$] Defina variáveis para as versões majoritárias do PostgreSQL:


462 18. Anexo 3 - Estratégias de atualização

read -p 'Digite a versão antiga: ' PGMAJOR_OLD

read -p 'Digite a versão nova: ' PGMAJOR_NEW

[$] Definição de variáveis de ambiente:

PGDATAOLD="/var/local/pgsql/${PGMAJOR_OLD}/data"
PGDATANEW="/var/local/pgsql/${PGMAJOR_NEW}/data"
PGBINOLD="/usr/local/pgsql/${PGMAJOR_OLD}/bin"
PGBINNEW="/usr/local/pgsql/${PGMAJOR_NEW}/bin"

[$] Parar os clusters:

${PGBINOLD}/pg_ctl -m f -D ${PGDATAOLD} stop

${PGBINNEW}/pg_ctl -m f -D ${PGDATANEW} stop

[$] Iniciar a atualização com o pg_upgrade:

pg_upgrade

. . .

No final, é informado que as estatísticas de otimização não foram transferidas. No diretório


de execução são deixados dois scripts: analyze_new_cluster.sh e delete_old_cluster.sh.

[$] Mudar a configuração de porta de escuta do cluster novo para a porta padrão:

sed -i 's/port = 5433/port = 5432/g' ${PGDATANEW}/postgresql.conf

[$] Iniciar o novo cluster:

${PGBINNEW}/pg_ctl -D ${PGDATANEW} start


463 18. Anexo 3 - Estratégias de atualização

[$] Script para estatísticas de otimização:

./analyze_new_cluster.sh

[$] Apagar o cluster antigo:

./delete_old_cluster.sh
464 18. Anexo 3 - Estratégias de atualização

PostgreSQL instalado via pacotes Debian

Instância antiga

Versão ${PGMAJOR_OLD}
Porta 5432
PGDATA /var/lib/postgresql/${PGMAJOR_OLD}/main
Binários /usr/lib/postgresql/${PGMAJOR_OLD}/bin

Instância nova

Versão ${PGMAJOR_NEW}
Porta 5433 (provisória)
PGDATA /var/lib/postgresql/${PGMAJOR_NEW}/main
Binários /usr/lib/postgresql/${PGMAJOR_NEW}/bin

[$] Defina variáveis para as versões majoritárias do PostgreSQL:

read -p 'Digite a versão antiga: ' PGMAJOR_OLD

read -p 'Digite a versão nova: ' PGMAJOR_NEW

[$] Definição de variáveis de ambiente:

export CONFIGOLD="/etc/postgresql/${PGMAJOR_OLD}/main"
export CONFIGNEW="/etc/postgresql/${PGMAJOR_NEW}/main"
export PGDATAOLD="/var/lib/postgresql/${PGMAJOR_OLD}/main"
export PGDATANEW="/var/lib/postgresql/${PGMAJOR_NEW}/main"
export PGBINOLD="/usr/lib/postgresql/${PGMAJOR_OLD}/bin"
export PGBINNEW="/usr/lib/postgresql/${PGMAJOR_NEW}/bin"

[$] Parar os clusters:

${PGBINOLD}/pg_ctl -m f -D ${PGDATAOLD} stop

${PGBINNEW}/pg_ctl -m f -D ${PGDATANEW} stop


465 18. Anexo 3 - Estratégias de atualização

[$] Iniciar a atualização com o pg_upgrade:

${PGBINNEW}/pg_upgrade \
-o "-c config_file=${CONFIGOLD}/postgresql.conf" \
-O "-c config_file=${CONFIGNEW}/postgresql.conf"

[$] Mudar a configuração de porta de escuta do cluster novo para a porta padrão:

sed -i "s/port = 5433/port = 5432/g" ${CONFIGNEW}/postgresql.conf

[$] Iniciar o novo cluster:

${PGBINNEW}/pg_ctl start -D ${CONFIGNEW}

[$] Script para estatísticas de otimização:

./analyze_new_cluster.sh

[$] Apagar o cluster antigo:

./delete_old_cluster.sh
466 18. Anexo 3 - Estratégias de atualização

PostgreSQL instalado via pacotes RedHat

Instância antiga

Versão ${PGMAJOR_OLD}
Porta 5432
PGDATA /var/lib/pgsql/${PGMAJOR_OLD}/data
Binários /usr/pgsql-${PGMAJOR_OLD}/bin

Instância nova

Versão ${PGMAJOR_NEW}
Porta 5433 (provisória)
PGDATA /var/lib/pgsql/${PGMAJOR_NEW}/data
Binários /usr/pgsql-${PGMAJOR_NEW}/bin

[$] Defina variáveis para as versões majoritárias do PostgreSQL:

read -p 'Digite a versão antiga: ' PGMAJOR_OLD

read -p 'Digite a versão nova: ' PGMAJOR_NEW

[$] Definição de variáveis de ambiente:

export PGDATAOLD="/var/lib/pgsql/${PGMAJOR_OLD}/data"
export PGDATANEW="/var/lib/pgsql/${PGMAJOR_NEW}/data"
export PGBINOLD="/usr/pgsql-${PGMAJOR_OLD}/bin"
export PGBINNEW="/usr/pgsql-${PGMAJOR_NEW}/bin"

[$] Parar os clusters:

${PGBINOLD}/pg_ctl -m f -D ${PGDATAOLD} stop

${PGBINNEW}/pg_ctl -m f -D ${PGDATANEW} stop


467 18. Anexo 3 - Estratégias de atualização

[$] Iniciar a atualização com o pg_upgrade:

${PGBINNEW}/pg_upgrade

[$] Mudar a configuração de porta de escuta do cluster novo para a porta padrão:

sed -i "s/port = 5433/port = 5432/g" ${PGDATANEW}/postgresql.conf

[$] Iniciar o novo cluster:

${PGBINNEW}/pg_ctl start -D ${PGDATANEW}

[$] Script para estatísticas de otimização:

./analyze_new_cluster.sh

[$] Apagar o cluster antigo:

./delete_old_cluster.sh
19
Anexo 4 - FDW: Foreign-Data
Wrappers
• Sobre Foreign-Data Wrappers
• postgres_fdw: acessando outro servidor PostgreSQL
• mysql_fdw: acessando o MySQL ou MariaDB
• file_fdw: acesso aos arquivos

468
469 19. Anexo 4 - FDW: Foreign-Data Wrappers

Sobre Foreign-Data Wrappers


Em 2003, houve uma nova especificação chamada SQL/MED (SQL Management of External
Data – Gerenciamento de Dados SQL Externos), que foi adicionada ao padrão SQL.
É uma forma padronizada de se lidar com acesso a objetos remotos vindos de bases de dados
SQL.
Em 2011, na versão 9.1 do PostgreSQL, foi lançado um suporte read-only (somente leitura)
desse padrão e, em 2013, o suporte a escrita foi adicionado na versão 9.3.
Hoje, há uma variedade de foreign-data wrappers (FDW) disponível, que habilita um servidor
PostgreSQL a acessar diferentes armazenamentos de dados remotos, desde outros bancos SQL
a um simples arquivo de texto.
Para encontrar esses FDWs, pode ser na Wiki do PostgreSQL [1] ou na PGXN [2]:

[1] http://wiki.postgresql.org/wiki/Foreign_data_wrappers
[2] http://pgxn.org

Aviso

Tenha em mente que a maioria dos FDWs não são suportados oficialmente pelo PGDG
(PostgreSQL Global Development Group) e que alguns desses projetos ainda estão em sua
versão beta.

Use com cuidado!


470 19. Anexo 4 - FDW: Foreign-Data Wrappers

postgres_fdw: acessando outro servidor PostgreSQL


A partir da versão 9.3 além de poder acessar outra base PostgreSQL também é possível fazer
gravações.
Na prática a seguir, um servidor (srv0) acessará outro (srv1), leitura e escrita.

Servidor IP Papel
srv0 192.168.56.70 Origem - postgres_fdw
srv1 192.168.56.71 Destino - PostgreSQL

Preparativos no servidor que vai ser acessado (srv1)

[$] [srv1] Permitir conexão no pg_hba.conf:

echo 'host all all 192.168.56.70/32 scram-sha-256' >> ${PGDATA}/pg_hba.conf

[$] [srv1] Editar o postgresql.conf:

vim ${PGDATA}/postgresql.conf

password_encryption = scram-sha-256

[$] [srv1] Recarregar as configurações:

pg_ctl reload

[$] [srv1] Definir uma senha para o usuário postgres:

psql -c "ALTER ROLE postgres ENCRYPTED PASSWORD '123';"

[>] [srv1] Criação da base de dados de teste:


471 19. Anexo 4 - FDW: Foreign-Data Wrappers

CREATE DATABASE db_teste2;


472 19. Anexo 4 - FDW: Foreign-Data Wrappers

[>] [srv1] Conectar à nova base criada:

\c db_teste2

[>] [srv1] Criação de tabela de teste:

CREATE TABLE tb_teste(


id SERIAL PRIMARY KEY,
campo_a INT,
campo_b VARCHAR(10));

Preparativos no servidor com FDW (srv0)

[>] [srv0] Criação da base de dados de teste:

CREATE DATABASE db_teste1;

[>] [srv0] Conectar à nova base criada:

\c db_teste1

[>] [srv0] Habilitando a extensão postgres_fdw na base de dados atual:

CREATE EXTENSION postgres_fdw;

[>] [srv0] Criação de servidor FDW:

CREATE SERVER srv_srv1


FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host '192.168.56.71', dbname 'db_teste2', port '5432');

[>] [srv0] Criação de mapeamento de usuários:


473 19. Anexo 4 - FDW: Foreign-Data Wrappers

CREATE USER MAPPING FOR postgres


SERVER srv_srv1
OPTIONS (user 'postgres', password '123');
474 19. Anexo 4 - FDW: Foreign-Data Wrappers

[>] [srv0] Criação da tabela estrangeira que vai se relacionar com a tabela criada em srv1:

CREATE FOREIGN TABLE ft_teste(


id INT,
campo_a INT,
campo_b VARCHAR(10))
SERVER srv_srv1
OPTIONS (table_name 'tb_teste', updatable 'true');

Testes

[>] [srv0] Inserção de um registro:

INSERT INTO ft_teste (campo_a, campo_b) VALUES (700, 'foo');

[>] [srv0] Consulta todos registros na tabela estrangeira:

SELECT * FROM ft_teste;

id | campo_a | campo_b
----+---------+---------
1 | 700 | foo

[>] [srv1] Consulta todos registros na tabela original:

SELECT * FROM tb_teste;

id | campo_a | campo_b
----+---------+---------
1 | 700 | foo
475 19. Anexo 4 - FDW: Foreign-Data Wrappers

[>] [srv0] Remover tudo o que foi criado para o teste de FDW:

-- Apaga a tabela estrangeira


DROP FOREIGN TABLE ft_teste;

-- Apaga o mapeamento de usuário


DROP USER MAPPING FOR postgres SERVER srv_srv1;

-- Apaga a configuração de servidor


DROP SERVER srv_srv1;

-- Desabilita a extensão na base de dados


DROP EXTENSION postgres_fdw;
476 19. Anexo 4 - FDW: Foreign-Data Wrappers

mysql_fdw: Acessando o MySQL ou MariaDB


O mysql_fdw é um foreign-data wrapper desenvolvido e mantido pela EnterpriseDB.
Seu código-fonte é aberto e pode ser obtido via instalação de pacotes ou no GitHub da empresa
desenvolvedora:

https://github.com/EnterpriseDB/mysql_fdw

Servidor IP Papel
srv0 192.168.56.70 Origem - mysql_fdw
srv1 192.168.56.71 Destino - MariaDB / MySQL

Preparativos no servidor MariaDB / MySQL (srv1)

[#] [srv1] Criar uma base de dados:

mysql -e 'CREATE DATABASE db_teste2;'

[#] [srv1] Acessar a nova base:

mysql db_teste2

[>] [srv1] Criação de usuário:

CREATE USER 'postgres'@'192.168.56.70' IDENTIFIED BY '123';

[>] [srv1] Conceder todos privilégios a todos objetos do banco ao usuário e servidor
PostgreSQL:

GRANT ALL PRIVILEGES ON db_teste2.* TO 'postgres'@'192.168.56.70';


477 19. Anexo 4 - FDW: Foreign-Data Wrappers

[>] [srv1] Criação de tabela:

CREATE TABLE tb_teste(


id INT AUTO_INCREMENT PRIMARY KEY,
campo_a INT,
campo_b VARCHAR(10));

[>] [srv1] Inserir uma linha na tabela:

INSERT INTO tb_teste (campo_a, campo_b) VALUES (700, 'foo');

Instalação do mysql_fdw no servidor PostgreSQL (srv0)

[#] [srv0] Variável de ambiente para pacotes:

PKG='make gcc libmariadb-dev git'

[#] [srv0] Instalação da parte de compilação e o código-fonte do MariaDB:

apt install -y ${PKG}

[#] [srv0] Baixar o repositório e acessar o diretório:

git clone https://github.com/EnterpriseDB/mysql_fdw.git /tmp/mysql_fdw && \


cd /tmp/mysql_fdw

[#] [srv0] Ler as variáveis de ambiente do usuário postgres:

source ~postgres/.pgvars

[#] [srv0] Definir a variável de ambiente para o caminho de bibliotecas C:

export C_INCLUDE_PATH="${C_INCLUDE_PATH}:/usr/include/mariadb"
478 19. Anexo 4 - FDW: Foreign-Data Wrappers

Deve-se informar onde está o arquivo mysql.h.


479 19. Anexo 4 - FDW: Foreign-Data Wrappers

[#] [srv0] Com a variável de ambiente USE_PGXS habilitada iniciar a compilação do FDW:

export USE_PGXS=1 && make && make install

[#] [srv0] Remover pacotes:

apt purge -y ${PKG}

[#] [srv0] Instalação de bibliotecas necessárias:

apt install -y libmariadb-dev-compat && apt clean

Configuração do mysql_fdw e testes

[$] [srv0] Criar base de dados:

createdb db_teste1

[$] [srv0] Acessar a base via psql:

psql db_teste1

[>] [srv0] Habilitar a extensão mysql_fdw na base:

CREATE EXTENSION mysql_fdw;

A extensão é o foreign-data wrapper.

[>] [srv0] Criar o objeto servidor:

CREATE SERVER srv_mysql


FOREIGN DATA WRAPPER mysql_fdw
OPTIONS (host '192.168.56.71', port '3306');
480 19. Anexo 4 - FDW: Foreign-Data Wrappers
481 19. Anexo 4 - FDW: Foreign-Data Wrappers

[>] [srv0] Criação de mapeamento de usuário:

CREATE USER MAPPING FOR postgres SERVER srv_mysql


OPTIONS (username 'postgres', password '123');

[>] [srv0] Criação de tabela estrangeira:

CREATE FOREIGN TABLE ft_teste(


id INT,
campo_a INT,
campo_b VARCHAR(10))
SERVER srv_mysql
OPTIONS (dbname 'db_teste2', table_name 'tb_teste');

[>] [srv0] Inserir uma linha na tabela estrangeira:

INSERT INTO ft_teste (id, campo_a, campo_b) VALUES (2, 500, ’’bar);

[>] [srv0] Consultar na tabela estrangeira:

SELECT * FROM ft_teste;

id | campo_a | campo_b
----+---------+---------
1 | 700 | foo
2 | 500 | bar

[>] [srv1] Consultar na tabela original:

SELECT * FROM tb_teste;

+----+---------+---------+
| id | campo_a | campo_b |
+----+---------+---------+
| 1 | 700 | foo |
| 2 | 500 | bar |
+----+---------+---------+
482 19. Anexo 4 - FDW: Foreign-Data Wrappers

file_fdw: Acesso a arquivos


O FDW file_fdw pode ser usado para acessar arquivos de dados no servidor, no sistema de
arquivos ou executar programas e pegar sua saída.
O arquivo de dados ou a saída do programa deve estar em um formato que possa ser lido pelo
comando COPY FROM.
Por enquanto, o acesso ao arquivo de dados é somente leitura.

https://www.postgresql.org/docs/current/static/file-fdw.html

[$] Criação do arquivo CSV de teste:

cat << EOF > /tmp/capitais_sudeste.csv


SP;São Paulo;12106920
RJ;Rio de Janeiro;6520266
MG;Belo Horizonte;2523794
ES;Vitória;363140
EOF

[>] No psql, habilitar a extensão file_fdw:

CREATE EXTENSION file_fdw;

[>] Configuração de servidor de FDW:

CREATE SERVER srv_srv1_file_fdw FOREIGN DATA WRAPPER file_fdw;

[>] Tabela estrangeira para o arquivo:

CREATE FOREIGN TABLE ft_capitais_sudeste(


id char(2),
nome VARCHAR(50),
populacao INT)
SERVER srv_srv1_file_fdw
OPTIONS (
filename '/tmp/capitais_sudeste.csv',
format 'csv',
delimiter ';');
483 19. Anexo 4 - FDW: Foreign-Data Wrappers

[>] Consulta à tabela estrangeira:

SELECT id, nome, populacao FROM ft_capitais_sudeste;

id | nome | populacao
----+----------------+-----------
SP | São Paulo | 12106920
RJ | Rio de Janeiro | 6520266
MG | Belo Horizonte | 2523794
ES | Vitória | 363140

[>] No psql, via shell do sistema operacional, apagar a primeira linha:

\! sed '1d' -i /tmp/capitais_sudeste.csv

[>] Verificando a tabela após a alteração do arquivo:

SELECT id, nome, populacao FROM ft_capitais_sudeste;

id | nome | populacao
----+----------------+-----------
RJ | Rio de Janeiro | 6520266
MG | Belo Horizonte | 2523794
ES | Vitória | 363140

Você também pode gostar