تحسين الأداء — من 100 إلى مليون QPS
تحسين الأداء مثل بناء طريق سريع — تبدأ بإصلاح أكثر الأقسام ازدحامًا (العنق الزجاجي)، وتقيس النتائج بعد إكمال كل قسم، بدلاً من توسيع الطريق بأكمله بشكل عشوائي. التحسين الأعمى إهدار؛ التحسين القائم على القياس هو النهج الأكثر كفاءة.
1. ما ستتعلمه
- استكشاف الحظر غير المتزامن: فخاخ
asyncioوإعادة كتابة الكود المتزامن باستخدامrun_in_executor - تحسين قاعدة البيانات: استراتيجيات الفهرسة، تحسين الاستعلامات، ضبط تجمع الاتصالات
- نموذج التزامن: عدد Uvicorn Workers وإعداد Gunicorn
- ضغط الاستجابة:
GZipMiddlewareوتحسين تسلسل JSON (orjson) - سيناريو Alice: مسار تحسين PriceTracker من 500 QPS على خادم واحد إلى مليون QPS في مجموعة
2. القصة الحقيقية لـ Alice
(1) نقطة الألم: 500 QPS على خادم واحد غير كافية
بعد إطلاق PriceTracker، لم يتمكن خادم واحد من التعامل سوى مع 500 QPS، لكن خلال أحداث التخفيضات الكبرى، ارتفعت حركة المرور إلى 5,000 QPS، مما تسبب في انتهاء مهل استجابة API. أضاف Charlie خوادم إضافية، لكن التحسن كان ضئيلاً — نُفد تجمع اتصالات قاعدة البيانات، والكود المتزامن كان يحظر حلقة الأحداث، وتسلسل JSON كان يستهلك 40% من المعالج.
(2) نهج منهجي لتحسين الأداء
تحسين الأداء ليس تخمينًا؛ إنه دورة من القياس ← تحديد العنق الزجاجي ← التحسين ← التحقق. نعالج المشاكل طبقة تلو الأخرى، من مستوى الكود (async/التسلسل) إلى مستوى قاعدة البيانات (الفهارس/تجمعات الاتصال) إلى المستوى المعماري (Workers/موازنة التحميل).
(3) العائد
تم تحسين QPS لـ PriceTracker من 500 إلى 10,000 (على خادم واحد)، ومع مجموعة موازنة تحميل، يمكن الوصول إلى مليون QPS، بينما انخفض زمن الاستجابة P99 من 500 مللي ثانية إلى 30 مللي ثانية.
3. مستويات تحسين الأداء
(1) نموذج التحسين ثلاثي الطبقات
graph TD
L3[طبقة البنية التحتية] --> L2[طبقة قاعدة البيانات]
L2 --> L1[طبقة التطبيق]
L1 --- A1[async/await - كشف الحظر]
L1 --- A2[التسلسل - orjson]
L1 --- A3[الضغط - GZip]
L2 --- D1[الفهارس - تحسين الاستعلامات]
L2 --- D2[تجمع الاتصالات - ضبط الحجم]
L2 --- D3[أنماط الاستعلام - منع N+1]
L3 --- I1[Workers - إعداد Gunicorn]
L3 --- I2[موازن التحميل - Nginx]
L3 --- I3[التوسع التلقائي - K8s HPA]
(2) مسارات تحسين QPS
flowchart LR
A[500 QPS\nخط الأساس] -->|async + orjson| B[2000 QPS]
B -->|فهارس قاعدة البيانات + التجمع| C[10000 QPS]
C -->|4 Workers| D[40000 QPS]
D -->|Nginx LB x 5| E[200000 QPS]
E -->|K8s x 5 pods| F[1000000 QPS]
| المرحلة | طريقة التحسين | QPS | عامل التحسين |
|---|---|---|---|
| خط الأساس | الإعداد الافتراضي | 500 | 1x |
| طبقة التطبيق | async + orjson | 2,000 | 4x |
| طبقة قاعدة البيانات | فهارس + تجمع اتصالات | 10,000 | 20x |
| Workers | 4 Workers + Gunicorn | 40,000 | 80x |
| موازنة التحميل | 5 نسخ Nginx | 200,000 | 400x |
| مرونة K8s | 5 Pods توسع تلقائي | 1,000,000 | 2000x |
4. تحسين طبقة التطبيق
(1) استكشاف الحظر غير المتزامن
(1) ▶مثال: كود متزامن يحظر حلقة الأحداث
import asyncio
import time
# سيء: الإدخال/الإخراج المتزامن يحظر حلقة الأحداث
async def get_price_bad(product_id: int):
time.sleep(0.1) # يحظر جميع الطلبات الأخرى لمدة 100 مللي ثانية!
return {"product_id": product_id, "price": 9.99}
# جيد: استخدم asyncio للإدخال/الإخراج
async def get_price_good(product_id: int):
await asyncio.sleep(0.1) # غير حظر، تستمر الطلبات الأخرى
return {"product_id": product_id, "price": 9.99}
الناتج:
# تم تعريف الدالة بنجاح
(2) ▶مثال: run_in_executor يُغلف الكود المتزامن
import asyncio
from functools import partial
# دالة متزامنة لا يمكن تحويلها إلى غير متزامنة
def sync_scrape_price(url: str) -> float:
# تستخدم مكتبة requests (متزامنة فقط)
import requests
response = requests.get(url, timeout=10)
return parse_price(response.text)
async def get_price_async(product_id: int):
loop = asyncio.get_event_loop()
# تشغيل الدالة المتزامنة في تجمع الخيوط - لا تحظر حلقة الأحداث
price = await loop.run_in_executor(
None, # تجمع الخيوط الافتراضي
partial(sync_scrape_price, f"https://api.example.com/prices/{product_id}"),
)
return {"product_id": product_id, "price": price}
الناتج:
# تم تعريف الدالة بنجاح
(2) تحسين تسلسل JSON
(3) ▶مثال: استخدام orjson بدلاً من json
# تثبيت: uv add orjson
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
app = FastAPI(default_response_class=ORJSONResponse)
@app.get("/api/v1/products")
async def list_products():
# orjson أسرع 3-10 مرات من stdlib json
return {"products": [{"id": i, "name": f"Product {i}"} for i in range(100)]}
الناتج:
# تم تعريف الدالة بنجاح
| مكتبة التسلسل | السرعة | الوصف |
|---|---|---|
| json (المكتبة القياسية) | مرجع القياس | تنفيذ Pure Python |
| orjson | 3-10x | منفذ بـ Rust، يتعامل تلقائيًا مع datetime |
| ujson | 2-3x | تنفيذ C، بعض مشاكل التوافق |
(4) ▶مثال: ضغط GZipMiddleware
from fastapi import FastAPI
from starlette.middleware.gzip import GZipMiddleware
app = FastAPI()
app.add_middleware(GZipMiddleware, minimum_size=1000) # ضغط الاستجابات الأكبر من 1 كيلوبايت
@app.get("/api/v1/products")
async def list_products():
# يتم ضغط استجابات JSON الكبيرة تلقائيًا
return {"products": [...]} # 50 كيلوبايت ← ~5 كيلوبايت مع gzip
الناتج:
# تم تعريف الدالة بنجاح
5. تحسين طبقة قاعدة البيانات
(1) استراتيجية الفهرسة
(1) ▶مثال: تصميم فهارس PriceTracker
from sqlalchemy import Index
class Product(Base):
__tablename__ = "products"
__table_args__ = (
# فهارس عمود واحد للفلاتر الشائعة
Index("ix_products_category", "category"),
Index("ix_products_created_at", "created_at"),
# فهرس مركب لاستعلامات الفئة + نطاق السعر
Index("ix_products_category_base_price", "category", "base_price"),
# فهرس جزئي للمنتجات النشطة فقط (خاص بـ PostgreSQL)
Index("ix_products_active", "created_at", postgresql_where=("deleted_at IS NULL")),
)
...
الناتج:
# التنفيذ ناجح
| نوع الفهرس | الاستعلامات المناسبة | مثال |
|---|---|---|
| فهرس عمود واحد | فلترة مساواة/نطاق | WHERE category = ? |
| فهرس مركب | تركيبات شروط متعددة | WHERE category = ? AND price > ? |
| فهرس جزئي | مجموعة فرعية مشروطة | WHERE deleted_at IS NULL |
| فهرس تغطي | تجنب البحث في الجدول | INCLUDE (name, price) |
(2) ضبط تجمع الاتصالات
(2) ▶مثال: إعداد تجمع اتصالات بمستوى الإنتاج
engine = create_async_engine(
DATABASE_URL,
pool_size=25, # اتصالات دائمة لكل Worker
max_overflow=10, # اتصالات إضافية عند نفاد التجمع
pool_timeout=30, # وقت الانتظار للحصول على اتصال متاح
pool_recycle=1800, # إعادة تدوير الاتصالات بعد 30 دقيقة
pool_pre_ping=True, # فحص الاتصال قبل الاستخدام
echo=False, # تعطيل تسجيل SQL في الإنتاج
)
الناتج:
# التنفيذ ناجح
| المعامل | القيمة الموصى بها | الوصف |
|---|---|---|
pool_size |
أنوية المعالج x 2 + 1 | عدد الاتصالات الأساسي |
max_overflow |
pool_size x 0.5 | أقصى عدد اتصالات الانفجار |
pool_timeout |
30 ثانية | مهلة الانتظار |
pool_recycle |
1800 ثانية | منع MySQL/PG من إغلاق الاتصالات الخاملة |
pool_pre_ping |
True | منع استخدام الاتصالات المقطوعة |
6. تحسين نموذج التزامن
(1) إعداد Workers
(1) ▶مثال: Gunicorn + Uvicorn Workers
# الإنتاج: يدير Gunicorn عمال Uvicorn
gunicorn app.main:app \
--workers 9 \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--timeout 120 \
--graceful-timeout 30 \
--access-logfile - \
--error-logfile -
الناتج:
# تم تنفيذ الأمر بنجاح
# في Dockerfile
CMD ["gunicorn", "app.main:app", \
"--workers", "9", \
"--worker-class", "uvicorn.workers.UvicornWorker", \
"--bind", "0.0.0.0:8000"]
| الإعداد | المعادلة/القيمة | الوصف |
|---|---|---|
| عدد Workers | (2 x CPU) + 1 |
4 أنوية = 9 عمال |
| worker-class | UvicornWorker | عامل ASGI |
| timeout | 120 ثانية | إنهاء Worker بسبب انتهاء المهلة |
| graceful-timeout | 30 ثانية | مهلة الإيقاف السلس |
| max-requests | 10,000 | إعادة تشغيل تلقائية لمنع تسرب الذاكرة |
❓أسئلة شائعة
run_in_executor؟min(32, os.cpu_count() + 4). للمهام كثيفة المعالج، استخدم ProcessPoolExecutor لتجنب قيود GIL.default_response_class=ORJSONResponse في FastAPI يُسلسل نماذج Pydantic تلقائيًا باستخدام orjson لإنتاج ناتج model_dump().max_connections في PostgreSQL هو 100؛ ستحتاج إلى زيادة أو تقليل pool_size وفقًا لذلك.📖ملخص
- نموذج ثلاثي الطبقات لتحسين الأداء: طبقة التطبيق (async/التسلسل) ← طبقة قاعدة البيانات (الفهارس/تجمع الاتصالات) ← طبقة البنية التحتية (Workers/LB)
- غلف الإدخال/الإخراج المتزامن بـ
run_in_executorلمنع حظر حلقة الأحداث - orjson أسرع 3 إلى 10 مرات من json في التسلسل، و GZipMiddleware يضغط الاستجابات الكبيرة
- تُصمم فهارس قاعدة البيانات بناءً على أنماط الاستعلام (فهارس عمود واحد، مركبة، وجزئية)؛ حجم تجمع الاتصالات يُنسق مع عدد Workers
- Gunicorn + Uvicorn Workers:
(2 x CPU) + 1عمال، والتوسع التلقائي K8s لمليون QPS
📝تمارين
- تمرين أساسي (الصعوبة: ⭐): ثبّت orjson، غيّر فئة الاستجابة الافتراضية في FastAPI إلى ORJSONResponse، واستخدم curl لمقارنة أوقات الاستجابة لـ JSON و orjson. تلميح:
default_response_class=ORJSONResponse - تمرين متقدم (الصعوبة ⭐⭐): أضف فهارس إلى جدول
productsفي PriceTracker (categoryوcreated_atوفهرس مركب منcategoryوbase_price)، وقارن أوقات تنفيذ نفس الاستعلام قبل وبعد إضافة الفهارس. تلميح:Index("ix_name", "col1", "col2")+EXPLAIN ANALYZE - تحدٍ (الصعوبة: ⭐⭐⭐): تحسين شامل للأداء — تسلسل orjson + GZipMiddleware + ضبط تجمع الاتصالات + Gunicorn مع 4 عمال. استخدم Locust لإجراء اختبارات الحمل وقارن QPS وزمن الاستجابة P99 قبل وبعد التحسين. تلميح:
locust -f locustfile.py --host=http://localhost:8000
---|



