Cara Mencegah Error max_connections saat Menghubungkan AWS Aurora Postgres di Vercel
Sebagian besar tutorial infrastruktur serverless hanya memperlihatkan proses menekan tombol koneksi beberapa kali. Masalah sebenarnya muncul setelah itu. Begitu kode dideploy dan trafik mulai sedikit melonjak, koneksi DB akan meledak, Anda berkeringat dingin karena mengelola Access Key statis, atau menerima faktur senilai ratusan dolar akibat biaya transfer data.
Berikut adalah rangkuman bottleneck praktis dan solusi yang dihadapi saat menghubungkan frontend Vercel dengan backend AWS.
1. Pengaturan Connection Pooling saat Menghubungkan Vercel dan AWS Aurora Postgres
PostgreSQL menjalankan proses baru setiap kali klien terhubung. Setiap proses mengonsumsi memori dari 2MB hingga lebih dari 8MB. Karena Vercel Serverless Functions menambah instans stateless tanpa batas saat ada permintaan masuk, batas max_connections pada Aurora Postgres (100–300 koneksi) dapat terpenuhi hanya dalam beberapa detik.
Pada akhirnya, error FATAL: sorry, too many clients already akan muncul, penggunaan CPU DB mencapai 100%, dan layanan pun mati.
Anda harus menempatkan PgBouncer di antara Serverless Functions dan Aurora, lalu mengaktifkan mode Transaction Pooling. Session pooling tidak membantu karena menahan koneksi secara 1:1, sedangkan statement pooling tidak mendukung transaksi multi-statement. Transaction pooling mengalokasikan koneksi DB hanya selama transaksi kueri berjalan dan langsung mengembalikannya ke pool segera setelah COMMIT dieksekusi.
Berikut adalah standar konfigurasi untuk file pgbouncer.ini.
`ini
[pgbouncer]
pool_mode = transaction
max_client_conn = 10000
default_pool_size = 20
reserve_pool_size = 10
max_db_connections = 80
`
- Aktifkan penggunaan kembali berbasis transaksi dengan
pool_mode = transaction.
- Atur
max_client_conn ke sekitar 10000 dan naikkan batas ulimit -n pada OS.
default_pool_size adalah jumlah koneksi yang dibuka ke Aurora per pasangan pengguna DB dan DB. Atur di antara 10 hingga 20.
- Menetapkan
max_db_connections secara ketat di angka 80 memungkinkan Anda mengontrol batas atas koneksi aktual yang masuk ke instans Aurora secara paksa.
Dalam lingkungan Vercel Fluid Compute, beberapa pemanggilan eksekusi berbagi cakupan global (global scope). Oleh karena itu, Anda harus mendeklarasikan pool driver DB pada cakupan modul. Menggunakan fungsi attachDatabasePool dari paket @vercel/functions akan membersihkan koneksi idle dengan rapi sebelum instans fungsi dimatikan.
`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;
`
Connection string juga harus dipisahkan berdasarkan lingkungan. Di lingkungan pengembangan lokal (.env.local), hubungkan langsung ke port Aurora (5432) atau arahkan ke PgBouncer lokal. Di lingkungan produksi (.env.production), gunakan port PgBouncer (6432) dan tambahkan parameter pgbouncer=true.
`bash
.env.local (Direct Connection)
DATABASE_URL="postgresql://dbuser:dbpassword@aurora-cluster.us-east-1.rds.amazonaws.com:5432/app_dev?sslmode=require"
.env.production (PgBouncer)
DATABASE_URL="postgresql://dbuser:dbpassword@pgbouncer-proxy.internal:6432/app_prod?sslmode=require&pgbouncer=true"
`
2. Mengelola Izin IAM menggunakan OIDC sebagai Pengganti Access Key
Menanamkan AWS_ACCESS_KEY_ID dan AWS_SECRET_ACCESS_KEY secara langsung di environment variable Vercel sangat berbahaya. Jika kunci bocor, seluruh infrastruktur Anda bisa terserang. Situasinya akan jauh lebih buruk jika Anda memberikan izin administrator seperti AdministratorAccess.
Gunakan OIDC (OpenID Connect). Metode ini mengirimkan JWT bertanda tangan yang diterbitkan Vercel ke API AssumeRoleWithWebIdentity milik AWS STS untuk mendapatkan kredensial sementara berdurasi 1 jam. Dengan ini, kebutuhan akan kunci ter-hardcode sepenuhnya hilang.
Hanya izinkan hak akses minimal (least privilege) pada kebijakan hubungan kepercayaan (trust relationship policy) AWS IAM Role.
`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"
}
}
}
]
}
`
- Daftarkan
[oidc.vercel.com/my-team-slug](https://oidc.vercel.com/my-team-slug) ke IAM Identity Provider.
- Batasi klaim
aud ke URL tim Vercel Anda untuk mencegah akses dari organisasi lain.
- Tentukan nama proyek (
my-ai-app) dan lingkungan (production) secara spesifik pada kondisi klaim sub. Ini adalah konfigurasi kunci untuk mencegah serangan Confused Deputy.
Kebijakan izin (permission policy) juga sebaiknya hanya membuka sumber daya yang diperlukan. Berikut adalah contoh pemberian izin hanya untuk bucket S3 tertentu dan eksekusi fungsi 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"
]
}
]
}
`
Dalam kode Node.js, gunakan @vercel/oidc-aws-credentials-provider untuk mengambil kredensial sementara.
`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()),
});
}
`
3. Mengurangi Latensi API Akibat Perbedaan Region
Jika frontend Vercel berjalan di PoP Tokyo dan backend AWS berada di AS Timur (us-east-1), latensi fisik sebesar 150ms hingga 250ms akan terakumulasi pada setiap kali permintaan bolak-balik.
Untuk respons API yang tidak sering berubah, sangat disarankan untuk melakukan caching pada tingkat Vercel Edge Middleware agar permintaan tidak diteruskan ke backend.
`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;
}
`
Dengan memberikan s-maxage=60, stale-while-revalidate=120, respons akan langsung diberikan dari Edge cache selama 60 detik. Setelah cache kadaluarsa, respons versi lama akan tetap ditampilkan hingga 120 detik ke depan sambil memperbarui cache di latar belakang.
Untuk menghubungkan tracing antar layanan, Anda harus menyebarkan spesifikasi W3C TraceContext dari OpenTelemetry (header traceparent). Berikut adalah konfigurasi 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'],
},
},
});
}
`
Jika Anda memasang penerima OpenTelemetry yang sama pada sisi AWS FastAPI, frontend hingga backend dapat terhubung dalam satu Trace ID tunggal.
`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]}
`
4. Mencegah Biaya Transfer Data dan Biaya Uncontrolled Scaling
Insiden biaya biasanya meledak di dua tempat: biaya kelebihan Fast Data Transfer Vercel (0,15perGBsetelahmelewati1TB)danbiayaDataTransferOutAWS(0,09 per GB). Ditambah lagi, jika Serverless Functions dan Lambda mulai melakukan autoscaling secara bersamaan saat trafik melonjak, nominal tagihan Anda bisa berubah drastis.
Berikut adalah contoh API Route untuk menerima webhook batas pengeluaran Vercel dan mengirimkan notifikasi ke Slack. Verifikasi tanda tangan HMAC SHA1 harus dilakukan agar tetap aman.
`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: Peringatan Pengeluaran Vercel: Batas anggaran (${event.payload.spendAmount} USD) telah tercapai.,
}),
});
return new Response('OK', { status: 200 });
}
`
Pastikan untuk mengaktifkan dua fitur keselamatan berikut.
- Aktifkan Pause production deployment di Settings > Billing pada proyek Vercel Anda. Walaupun layanan akan terhenti saat batas anggaran tercapai, saldo rekening bank Anda akan tetap aman.
- Batasi Reserved Concurrency pada AWS Lambda ke sekitar 50. Ini akan mencegah situasi di mana instans bertambah tanpa batas, menekan DB, dan memicu penagihan biaya secara bersamaan.