Como evitar o erro max_connections ao conectar a Vercel ao AWS Aurora Postgres
TuBrief 편집팀
2026년 7월 21일
0
Computing/Software원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
A maioria dos tutoriais de infraestrutura serverless mostra apenas o processo de clicar em alguns botões de conexão. O problema vem a seguir. Assim que você faz o deploy do código e o tráfego aumenta um pouco, as conexões com o banco de dados caem, você começa a suar frio gerenciando Access Keys estáticas ou recebe uma fatura de centenas de dólares devido aos custos de transferência de dados.
Organizei aqui os gargalos práticos e as soluções que enfrentei ao integrar o front-end da Vercel com o back-end da AWS.
O PostgreSQL cria um novo processo para cada cliente conectado. Cada processo consome de 2 MB a mais de 8 MB de memória. Como as Serverless Functions da Vercel escalam instâncias stateless ilimitadamente à medida que as requisições chegam, o limite de max_connections do Aurora Postgres (100–300 conexões) é atingido em questão de segundos.
No fim, surge o erro FATAL: sorry, too many clients already, o uso de CPU do banco de dados bate 100% e o serviço cai.
É necessário colocar o PgBouncer entre as Serverless Functions e o Aurora e ativar o modo Transaction Pooling (Pooling de Transação). O Session Pooling mantém uma conexão 1:1, portanto não ajuda, e o Statement Pooling não suporta transações de múltiplas instruções. O Transaction Pooling aloca a conexão com o banco de dados apenas durante a execução da transação da query e a devolve ao pool imediatamente após o COMMIT.
Aqui estão os parâmetros recomendados para o arquivo pgbouncer.ini:
`ini
[pgbouncer]
pool_mode = transaction
max_client_conn = 10000
default_pool_size = 20
reserve_pool_size = 10
max_db_connections = 80
`
pool_mode = transaction.max_client_conn em cerca de 10000 e aumente o limite de ulimit -n do sistema operacional.default_pool_size é o número de conexões a serem mantidas abertas no Aurora por par de usuário/banco de dados. Mantenha entre 10 e 20.max_db_connections exatamente em 80 força o controle do limite superior de conexões reais que entram na instância do Aurora.No ambiente Fluid Compute da Vercel, múltiplas invocações de execução compartilham o escopo global. Portanto, você deve declarar o pool do driver do banco de dados no escopo do módulo. O uso da função attachDatabasePool do pacote @vercel/functions garante o encerramento correto de conexões ociosas antes que a instância da função seja desligada.
`typescript
import { Pool } from 'pg';
import { attachDatabasePool } from '@vercel/functions';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10,
idleTimeoutMillis: 5000,
});
attachDatabasePool(pool);
export default pool;
`
A string de conexão também deve ser separada por ambiente. No ambiente de desenvolvimento local (.env.local), conecte-se diretamente à porta do Aurora (5432) ou aponte para o PgBouncer local; em produção (.env.production), aponte para a porta do PgBouncer (6432) e adicione o parâmetro pgbouncer=true.
`bash
DATABASE_URL="postgresql://dbuser:dbpassword@aurora-cluster.us-east-1.rds.amazonaws.com:5432/app_dev?sslmode=require"
DATABASE_URL="postgresql://dbuser:dbpassword@pgbouncer-proxy.internal:6432/app_prod?sslmode=require&pgbouncer=true"
`
Inserir AWS_ACCESS_KEY_ID e AWS_SECRET_ACCESS_KEY diretamente nas variáveis de ambiente da Vercel é perigoso. Se a chave for vazada, toda a sua infraestrutura ficará comprometida. A situação é ainda pior se você tiver concedido permissões administrativas como AdministratorAccess.
Utilize OIDC (OpenID Connect). Esse método envia um JWT assinado emitido pela Vercel para a API AssumeRoleWithWebIdentity do AWS STS para obter credenciais temporárias válidas por 1 hora. Isso elimina totalmente a necessidade de ter chaves hardcoded.
A política de confiança da IAM Role na AWS deve definir apenas as permissões mínimas necessárias.
`json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.vercel.com/my-team-slug"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.vercel.com/my-team-slug:aud": "https://vercel.com/my-team-slug",
"oidc.vercel.com/my-team-slug:sub": "owner:my-team-slug:project:my-ai-app:environment:production"
}
}
}
]
}
`
[oidc.vercel.com/my-team-slug](https://oidc.vercel.com/my-team-slug) no IAM Identity Provider.aud à URL da sua equipe na Vercel para impedir o acesso de outras organizações.my-ai-app) e o ambiente (production) na condição da claim sub. Essa é a configuração chave para prevenir ataques do tipo Confused Deputy.A política de permissão também deve liberar apenas os recursos necessários. Veja um exemplo que concede permissão apenas para um bucket S3 específico e invocações de funções Lambda:
`json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RestrictedS3BucketAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-production-ai-assets",
"arn:aws:s3:::my-production-ai-assets/*"
]
},
{
"Sid": "RestrictedLambdaInvocation",
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": [
"arn:aws:lambda:us-east-1:123456789012:function:python-ml-inference-service"
]
}
]
}
`
No código Node.js, utilize @vercel/oidc-aws-credentials-provider para obter as credenciais temporárias.
`typescript
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda';
import { awsCredentialsProvider } from '@vercel/oidc-aws-credentials-provider';
const credentials = awsCredentialsProvider({
roleArn: process.env.AWS_ROLE_ARN!,
});
const s3Client = new S3Client({ region: 'us-east-1', credentials });
const lambdaClient = new LambdaClient({ region: 'us-east-1', credentials });
export async function POST(req: Request) {
const body = await req.json();
await s3Client.send(new PutObjectCommand({
Bucket: 'my-production-ai-assets',
Key: inputs/${Date.now()}.json,
Body: JSON.stringify(body),
}));
const lambdaRes = await lambdaClient.send(new InvokeCommand({
FunctionName: 'python-ml-inference-service',
Payload: Buffer.from(JSON.stringify(body)),
}));
return Response.json({
status: 'success',
result: JSON.parse(Buffer.from(lambdaRes.Payload!).toString()),
});
}
`
Se o front-end da Vercel estiver rodando no PoP de Tóquio e o back-end da AWS estiver no leste dos EUA (us-east-1), haverá um acréscimo de 150 ms a 250 ms de latência física a cada requisição.
Para respostas de API que não mudam com frequência, o ideal é fazer o cache no Vercel Edge Middleware para evitar que a chamada chegue até o back-end.
`typescript
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export const config = {
matcher: ['/api/v1/ml-models/:path*'],
};
export function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set(
'Cache-Control',
'public, s-maxage=60, stale-while-revalidate=120'
);
response.headers.set(
'Vercel-CDN-Cache-Control',
's-maxage=300, stale-while-revalidate=600'
);
return response;
}
`
Ao configurar s-maxage=60, stale-while-revalidate=120, a resposta é servida instantaneamente do cache Edge por 60 segundos. Após a expiração do cache, a versão antiga continua sendo servida por até 120 segundos enquanto o cache é atualizado em segundo plano.
Para conectar o rastreamento (tracing) entre serviços, é necessário propagar o padrão W3C TraceContext do OpenTelemetry (cabeçalho traceparent). Veja a configuração na Vercel:
`typescript
// instrumentation.ts
import { registerOTel } from '@vercel/otel';
export function register() {
registerOTel({
serviceName: 'vercel-frontend-service',
instrumentationConfig: {
fetch: {
propagateContextUrls: ['api.my-aws-backend.com', '*.amazonaws.com'],
},
},
});
}
`
Ao adicionar um receptor OpenTelemetry equivalente no lado do FastAPI na AWS, o fluxo do front-end ao back-end é conectado sob um único Trace ID.
`python
from fastapi import FastAPI
from opentelemetry import trace
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
provider = TracerProvider()
processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="http://otel-collector:4318/v1/traces"))
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
app = FastAPI(title="Python ML Service")
FastAPIInstrumentor.instrument_app(app)
@app.get("/api/v1/ml-models/predict")
async def predict():
tracer = trace.get_tracer(name)
with tracer.start_as_current_span("ml_inference_execution"):
return {"status": "completed", "prediction": [0.95, 0.05]}
`
Erros de custo geralmente acontecem em dois lugares: na taxa de excedente do Fast Data Transfer da Vercel (0,09 por GB). Somado a isso, se as Serverless Functions e o Lambda começarem a escalar simultaneamente durante picos de tráfego, o valor da fatura muda de patamar.
Aqui está um exemplo de API Route que recebe webhooks de limites de gastos da Vercel e envia notificações para o Slack. É importante validar a assinatura HMAC SHA1 para garantir a segurança.
`typescript
import crypto from 'crypto';
export async function POST(req: Request) {
const payload = await req.text();
const signature = req.headers.get('x-vercel-signature');
const expectedSignature = crypto
.createHmac('sha1', process.env.VERCEL_SPEND_WEBHOOK_SECRET!)
.update(payload)
.digest('hex');
if (signature !== expectedSignature) {
return new Response('Invalid Signature', { status: 401 });
}
const event = JSON.parse(payload);
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: Alerta de gastos na Vercel: Limite do orçamento (${event.payload.spendAmount} USD) atingido.,
}),
});
return new Response('OK', { status: 200 });
}
`
Certifique-se de ativar estas duas travas de segurança: