كيفية منع خطأ max_connections عند ربط AWS Aurora Postgres بـ Vercel
TuBrief 편집팀
2026년 7월 21일
0
Computing/Software원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
معظم الدروس التعليمية للبنية التحتية الخالية من الخوادم (Serverless) تعرض فقط عملية النقر على أزرار الاتصال عدة مرات. ولكن المشكلة تبدأ بعد ذلك. بمجرد نشر الكود وتلقي حركة مرور (Traffic) بسيطة، ينفجر اتصال قاعدة البيانات، أو تتصبب عرقًا أثناء إدارة مفاتيح الوصول الثابتة (Access Keys)، أو تتلقى فاتورة مادية تبلغ مئات الدولارات بسبب تكاليف نقل البيانات.
لقد قمت بتلخيص اختناقات العمل الفعلية والحلول التي تمت مواجهتها عند الربط بين واجهة Vercel الأمامية وواجهة AWS الخلفية.
أنشئ PostgreSQL عملية جديدة في كل مرة يتصل فيها العميل. وتستهلك كل عملية ما بين 2 ميجابايت إلى أكثر من 8 ميجابايت من الذاكرة. نظرًا لأن وظائف Vercel Serverless تقوم بزيادة المثيلات عديمة الحالة (Stateless Instances) بلا حدود عند وصول الطلبات، فإنها تملأ الحد الأقصى لـ max_connections في Aurora Postgres (100-300 اتصال) خلال ثوانٍ معدودة.
في النهاية، يظهر خطأ FATAL: sorry, too many clients already وتصل نسبة استخدام وحدة المعالجة المركزية (CPU) لقاعدة البيانات إلى 100% ويتوقف المرفق عن العمل.
يجب وضع PgBouncer بين وظائف Serverless و Aurora وتفعيل وضع تجميع المعاملات (Transaction Pooling). تجميع الجلسات (Session Pooling) لا يساعد لأنه يحتفظ بالاتصالات بنسبة 1:1، وتجميع العبارات (Statement Pooling) لا يمكنه استخدام معاملات متعددة العبارات. يقوم تجميع المعاملات بتخصيص اتصال قاعدة البيانات فقط أثناء تنفيذ معاملة الاستعلام، وبمجرد إرسال COMMIT يتم إعادته فورًا إلى المجمع (Pool).
هذه هي معايير الإعداد لملف 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 عند حوالي 10000 وقم بزيادة حد ulimit -n في نظام التشغيل.default_pool_size هو عدد الاتصالات المراد فتحها لـ Aurora لكل زوج من مستخدم DB وقاعدة البيانات. اضبطه بين 10 و 20.max_db_connections على 80 تمامًا، يمكنك تقييد الحد الأقصى للاتصالات الفعلية الداخلة إلى مثيل Aurora بشكل إجباري.في بيئة Vercel Fluid Compute، تتشارك عمليات التنفيذ المتعددة النطاق العالمى (Global Scope). لذلك، يجب التصريح عن مجمع محرك قاعدة البيانات (DB Driver Pool) في نطاق الوحدة (Module Scope). باستخدام حزمة @vercel/functions والدالة attachDatabasePool، يمكنك تنظيف الاتصالات الخاملة بشكل نظيف قبل إيقاف تشغيل مثيل الدالة.
`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;
`
يجب أيضًا تقسيم سلسلة الاتصال حسب البيئة. في بيئة التطوير المحلية (.env.local)، قم بالاتصال مباشرة بمنفذ Aurora (5432) أو توجيهه إلى PgBouncer المحلي، وفي بيئة الإنتاج (.env.production)، قم بإضافة منفذ PgBouncer (6432) والبارامتر 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"
`
من الخطير إدخال AWS_ACCESS_KEY_ID و AWS_SECRET_ACCESS_KEY مباشرة في متغيرة بيئة Vercel. إذا تسرب المفتاح، فستتم اختراق البنية التحتية بالكامل. إذا كنت قد منحت أذونات مسؤول مثل AdministratorAccess، فإن الوضع يصبح أكثر خطورة.
استخدم OIDC (OpenID Connect). إنها طريقة يتم فيها إرسال رمز JWT الموقّع والصادر من Vercel إلى API الخاص بـ AssumeRoleWithWebIdentity في AWS STS للحصول على أوراق اعتماد مؤقتة لمدة ساعة واحدة. يتلاشى المفتاح الثابت المكتوب في الكود بنفسه.
يجب تعريف الحد الأدنى من الأذونات فقط في سياسة علاقة الثقة لـ 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"
}
}
}
]
}
`
[oidc.vercel.com/my-team-slug](https://oidc.vercel.com/my-team-slug) في IAM Identity Provider.aud إلى URL فريق Vercel لمنع المؤسسات الأخرى من الوصول.my-ai-app) والبيئة (production) بدقة كشرط لمطالبة sub. هذا هو الإعداد الرئيسي لمنع هجمات Confused Deputy.تفتح سياسة الأذونات الموارد المطلوبة فقط. فيما يلي مثال لمنح أذونات لطلب حاوية S3 محددة ودالة 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"
]
}
]
}
`
في كود Node.js، استخدم @vercel/oidc-aws-credentials-provider لجلب أوراق الاعتماد المؤقتة.
`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()),
});
}
`
إذا كانت واجهة Vercel الأمامية تعمل في طوكيو PoP والواجهة الخلفية لـ AWS في شرق الولايات المتحدة (us-east-1)، يضاف زمن تأخير فيزيائي يتراوح بين 150 مللي ثانية و 250 مللي ثانية مع كل طلب يمر بينهما.
من الأفضل تخزين استجابات API التي لا تتغير كثيرًا مؤقتًا على مستوى Vercel Edge Middleware لتجنب استدعاء الواجهة الخلفية نفسها.
`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;
}
`
إذا قمت بضبط s-maxage=60, stale-while-revalidate=120، فسيتم تقديم الاستجابة فورًا من ذاكرة التخزين المؤقت في Edge لمدة 60 ثانية، ولمدة تصل إلى 120 ثانية بعد انتهاء صلاحية التخزين المؤقت، ستظهر الاستجابة القديمة أولاً بينما يتم تحديث ذاكرة التخزين المؤقت في الخلفية.
لربط التتبع بين الخدمات، يجب نشر معيار W3C TraceContext الخاص بـ OpenTelemetry (ترويسة traceparent). فيما يلي إعدادات 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'],
},
},
});
}
`
إذا قمت أيضًا بتثبيت مستقبل OpenTelemetry على مستوى AWS FastAPI بنفس الطريقة، فسيتم الربط بـ 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]}
`
تحدث حوادث التكلفة عادةً في مكانين: رسوم Fast Data Transfer الإضافية من Vercel (0.15 دولار لكل جيجابايت بعد تجاوز 1 ترابايت) ورسوم Data Transfer Out من AWS (0.09 دولار لكل جيجابايت). يضاف إلى ذلك عندما يبدأ التوسع المتزامن لوظائف Serverless و Lambda أثناء ازدحام حركة المرور، تتغير فئة الفاتورة تمامًا.
فيما يلي مثال على API Route يتلقى Webhook لحد الإنفاق في Vercel ويرسل إشعارًا إلى Slack. يجب أن يمر عبر التحقق من توقيع HMAC SHA1 ليكون آمنًا.
`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: تنبيه إنفاق Vercel: تم الوصول إلى حد الميزانية (${event.payload.spendAmount} USD).,
}), });
return new Response('OK', { status: 200 });
}
`
تأكد من تفعيل إعدادي الأمان هذين.