Nosso artigo sobre Compatibilidade com PostgREST detalha o que o servidor cobre funcionalmente (filtros, incorporação, RPC, RLS) e o que ele deixa para você. Este vem de outro lugar. Ele documenta, com o código Aurabase e a documentação oficial do PostgREST como fontes, onde e por que o PostgREST realmente estagna em escala. Não estamos reproduzindo aqui um banco de carga que não administramos. Nossa metodologia de benchmark explica por que um número isolado, sem um protocolo publicado, não nos parece confiável.
O essencial
- O próprio PostgREST é leve: 50 a 250 milicores de CPU, 64 a 128 MB de RAM por réplica em instâncias dedicadas do Aurabase. A taxa de transferência HTTP bruta quase nunca é o fator limitante na produção.
- O teto real é o orçamento de conexão do Postgres:
PGRST_DB_POOL× réplicas. Verificado no código Aurabase: 20 conexões por projeto no nível dedicado (10×2), 4 no nível compartilhado (2×2). Esta é uma escolha deliberada para acomodar mais inquilinos no mesmomax_connections. Prefer: count=exactforça uma varredura MVCC cara em tabelas grandes. PostgREST documenta duas alternativas mais baratas:count=plannedecount=estimated, custando um total aproximado.- Um teto
db-max-rows(1000 linhas por padrão no Aurabase) trunca uma resposta SEM relatá-la emContent-Range(medido em condições reais, detalhado abaixo). - Após uma migração DDL, o cache do esquema PostgREST é recarregado de forma assíncrona. O gateway Aurabase tenta até 8 vezes (cerca de 3,5 segundos cumulativos no pior caso) antes de desistir, um comportamento documentado diretamente no código.
O que um benchmark PostgREST mede e o que não mede
Um teste de taxa de transferência HTTP no PostgREST mede principalmente o Postgres, raramente o PostgREST. O servidor é uma fina camada de tradução na frente da base. Na grande maioria das cargas do mundo real, o tempo de resposta é dominado pela consulta SQL executada, não pelo processo que a gerou.
O projeto PostgREST mantém um repositório dedicado para este tópico, PostgREST/postgrest-benchmark no GitHub, que rastreia variações de rendimento de lançamento para lançamento, em vez de publicar uma figura de marketing isolada. Não o executamos nem o republicamos aqui. Seus resultados dependem do hardware, do tamanho do esquemático e do cenário testado, exatamente as variáveis que nosso próprio protocolo de benchmark exige que sejam documentadas antes de citar uma figura.
Abaixo do PostgREST, está pgbench que mede a camada que realmente importa: tempo de transação SQL sob carga simultânea. Esta é a ferramenta oficial de benchmark do PostgreSQL (postgresql.org/docs/current/pgbench.html, acessado em 24 de agosto de 2026). Em vez de reproduzir este protocolo aqui, este artigo documenta quatro limitações arquitetônicas concretas do PostgREST em produção, cada uma verificada no código-fonte do Aurabase ou na documentação oficial do projeto.
A pegada real de uma instância PostgREST no Aurabase
Cada projeto do mecanismo Aurabase Postgres recebe duas réplicas PostgREST dedicadas, co-localizadas com seu cluster. O manifesto do Kubernetes que os implanta define recursos modestos.
O que essas réplicas realmente consomem não é CPU: são conexões com o primário do Postgres. Cada instância PostgREST se conecta diretamente ao primário (-rw), sem passar pelo pooler PgBouncer implantado para o locatário. Esta escolha já está detalhada em nosso artigo sobre Compatibilidade com PostgREST: o mecanismo de recarregamento de esquema LISTEN/NOTIFY requer uma conexão persistente, incompatível com um pooler em modo de transação. O que este artigo acrescenta: quanto custa realmente, em termos de conexões, e onde atinge o pico.
O tamanho deste pool por réplica (PGRST_DB_POOL) difere deliberadamente dependendo do nível do projeto, verificado em k8s_tenant.rs, a função que constrói o manifesto PostgREST para cada projeto:
| Rolamento | PGRST_DB_POOL/réplica | Réplicas | Conexões/projeto despertado |
|---|---|---|---|
| Dedicado (premium, A1) | 10 (padrão PostgREST) | 2 | 20 |
| Compartilhado (frota, gratuito/profissional/equipe) | 2 (padrão Aurabase, reduzido) | 2 | 4 |
No nível dedicado, a restrição é relaxada: um projeto possui seu próprio cluster CNPG, portanto seu próprio max_connections, sem vizinhos de sobra. Ao nível partilhado, vários projetos de uma mesma organização partilham um único cluster: é este contexto que torna decisivo o orçamento das ligações, desenvolvido na secção seguinte.
O orçamento de conexões decide quantos locatários estão funcionando ao mesmo tempo
Em um cluster Postgres compartilhado, não é a taxa de transferência HTTP que limita o número de projetos ativos simultaneamente. Este é o número de conexões que suas instâncias PostgREST mantêm abertas no primário, em comparação com o max_connectionsdisponível.
Aurabase deriva esse orçamento diretamente dos limites reais do cluster, verificados em fleet.rs: (max_connections − réserve superuser/CNPG − connexions réservées au pooler) ÷ connexions par projet éveillé, piso em 1. A reserva fixa é de 10 conexões (superusuário, gerenciador de instância CNPG, exportador de métricas, margem de administração do provisionador). Nos defeitos entregues (pool de 2 por réplica, 2 réplicas ou 4 conexões por projeto despertado), o cálculo fornece três orçamentos diferentes dependendo do nível de dimensionamento do cluster.
Fonte: derivado de fleet.rs::derive_wake_budget e wake_budget_for_org_plan, código Aurabase, relido em 24 de agosto de 2026.
Este orçamento não é uma cota de projetos próprios: uma organização team pode manter 50 projetos, a maioria dos quais estão inativos. Este é um limite de simultaneidade : o número de projetos que podem manter conexões abertas ao mesmo tempo no primário. Um despertar acima do orçamento não falha, é adiado até que um projeto irmão volte a dormir, verificado no mesmo arquivo. O tópico de dimensionamento max_connections em si é expandido em nosso artigo sobre ajuste de max_connections, e a compensação dedicada/mutualizada como um todo em base dedicada vs. base compartilhada.
Por que preferir: count=exact retarda uma consulta em uma tabela grande
Pedir um total exato força o Postgres a contar as linhas visíveis do resultado filtrado em cada consulta, um custo que cresce com a tabela, e não uma operação gratuita.
O PostgreSQL não mantém nenhum contador de linha indexado pronto para uso. No MVCC, a visibilidade de uma linha depende da transação que a lê. Um COUNT(*) exato deve, portanto, visitar as linhas candidatas em vez de ler um valor pré-computado. Esta é uma limitação estrutural bem documentada no ecossistema Postgres, incluindo fornecedores de análises como ClickHouse, que comparam seus próprios contadores aproximados com o comportamento transacional do Postgres.
PostgREST documenta essas três estratégias de contagem nativamente (postgrest.org, acessado em 24 de agosto de 2026). A estratégia exact garante um total ao preço da varredura. planned retorna uma estimativa quase gratuita do planejador de consultas. estimated alterna automaticamente entre os dois com base em um limite. A escolha não é cosmética: uma paginação que requer count=exact numa tabela de vários milhões de linhas paga por esta verificação em cada página, mesmo quando o utilizador nunca consulta a última.
O truncamento de db-max-rows é invisível sem count=exact
Um limite de linha pode truncar uma resposta PostgREST sem qualquer indicação no corpo ou nos cabeçalhos, a menos que solicite explicitamente um total exato. Medimos isso em condições reais em uma instância dedicada do Aurabase, não presumida.
Em uma tabela de teste de 10 linhas com PGRST_DB_MAX_ROWS=5, PostgREST v12.2.3 renderiza exatamente o mesmo cabeçalho Content-Range para duas situações muito diferentes:
| Consulta | Linhas renderizadas | Faixa de conteúdo | meta (Aurabase) |
|---|---|---|---|
| ?limit=50 (sem contagem) | 5/10 reais | 0-4/* | {} |
| ?limit=50&count=exato | 5/10 reais | 0-4/10 | {total: 10} |
Sem count=exact, a resposta de 5 linhas é indistinguível de uma tabela que realmente conteria apenas 5: Content-Range: 0-4/* descreve as linhas renderizadas, nunca o limite aplicado. O limite real em vigor não aparece em nenhum lugar neste caso, medido diretamente no caminho PostgREST do SDK Aurabase.
Se sua implantação PostgREST definir um db-max-rows (o padrão do Aurabase é 1000), um cliente que compara data.length ao limite solicitado para detectar uma página inteira pode estar enganado. O erro aparece assim que o teto do servidor for inferior a esse limite. O único sinal confiável é comparar o número de linhas recebidas com o total retornado por count=exact, o que coloca em jogo diretamente a compensação de custos descrita na seção anterior.
Recarregando o cache do esquema após uma migração
PostgREST mantém o esquema Postgres na memória na inicialização. Após um DDL (criar tabela, adicionar coluna), esse cache deve ser recarregado antes que a nova rota responda, e esse recarregamento é assíncrono.
Uma gravação que chega nesta janela pode receber um transitório 404 (cache ainda não atualizado), mesmo que a tabela realmente exista no lado do Postgres. O gateway Aurabase absorve isso com um loop de repetição limitado, verificado em postgrest_proxy.rs: até 8 tentativas, aumentando a espera (250 ms mais 100 ms por tentativa), 3,5 segundos cumulativos no pior caso. Este mecanismo afeta apenas gravações, nunca leituras.
O gateway não emite nenhum sinal de recarga: apenas espera. O único gatilho real é um pg_notify('pgrst', 'reload schema') emitido pelo serviço de banco de dados no caminho DDL. Se um caminho de migração se esquecer de emitir este sinal, as 8 tentativas se esgotam em um cache que nunca mudará, risco documentado como está no comentário do código, não disfarçado.
Para uma implantação PostgREST auto-hospedada, a lição é generalizada. Cada caminho DDL em seu aplicativo deve acionar o recarregamento, via NOTIFY ou um sinal SIGUSR1 para o processo. Caso contrário, uma migração produz um pico de latência p99 disfarçado como erros intermitentes logo após a implantação.
O que a arquitetura corta, não o rendimento bruto
As quatro limitações documentadas aqui compartilham uma coisa em comum: nenhuma é vista em um teste de taxa de transferência HTTP isolado, mas todas as quatro determinam se uma implantação do PostgREST pode ser escalada para produção.
- Orçamento de conexão: limita o número de locatários ativos simultaneamente em um cluster compartilhado, independentemente da taxa de transferência por locatário.
- Custo de COUNT exato: cresce com a tabela, não com a carga; é ignorado com
planned/estimated. - Truncamento silencioso: um limite de linha configurado corretamente ainda pode interromper a paginação mal instrumentada.
- Recarga do esquema: uma janela de latência após cada migração, limitada se o sinal de recarga estiver bem conectado, ilimitada caso contrário.
Esteja você escolhendo entre PostgREST auto-hospedado, uma camada GraphQL estilo Hasura ou uma API personalizada, esses quatro eixos são um melhor ponto de comparação do que uma figura de req/s isolada. Veja nossa comparação PostgREST vs Hasura vs API personalizada. A escolha do pooler que fica na frente do seu banco de dados é igualmente importante: nossa comparação PgBouncer vs Supavisor vs PgCat detalha porque o PostgREST não pode passar por um pooler no modo de transação.
Perguntas frequentes
O que lembrar
O PostgREST quase nunca quebra apenas sob carga HTTP: sua arquitetura é simples demais para isso. O que quebra a produção é o que a rodeia: quantas conexões suas réplicas mantêm abertas, quanto custa um total exato. Isto também inclui se um truncamento permanece visível e quanto tempo dura a janela após uma migração.
Essas quatro limitações não são específicas do Aurabase: elas se aplicam a qualquer implantação PostgREST, auto-hospedada ou gerenciada. O que esse código mostra é como uma implantação multilocatário os torna explícitos, em vez de deixá-los surpreendentes na produção.