אופטימיזציה של Django ORM עבור יישומי Python

האם אתר ה-Django שלך על PostgreSQL מאט בכל עמוד, ומשתמשים עוזבים לפני שמגיעה תגובה? אנחנו מגדירים את Django ORM במתכונת turnkey: מבטלים שאילתות N+1, מוסיפים אינדקסים ומכוונים את מאגר החיבורים. הצוות שלנו מטפל בכל המחזור—מביקורת ועד יישום—ומבטיח ביצועים יציבים ותמיכה מתמשכת.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
אופטימיזציה של Django ORM עבור יישומי Python
בינוני
~1 יום

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1365
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113
  • פיתוח אתר עבור SBH Partners
    פיתוח אתר עבור SBH Partners
    1201
  • פיתוח אתר עבור Red Pear
    פיתוח אתר עבור Red Pear
    619

תארו לעצמכם אתר Django על PostgreSQL שמאט בכל עמוד. שאילתות N+1 מתרבות, אינדקסים חסרים, חיבור pool לא מוגדר — TTFB עולה על 2 שניות, Core Web Vitals נכשלים, משתמשים עוזבים. זה כואב במיוחד בעמודי קטלוג עם אלפי מוצרים: שאילתת קטגוריה אחת מייצרת מאות שאילתות משנה. ללא ארכיטקטורת ORM נכונה, הפרויקט הופך לבלאגן של שאילתות, והעלות של כל שיפור עולה.

אנחנו, מהנדסים מוסמכים עם 10 שנות ניסיון, מגדירים Django ORM turnkey. אנו מבטיחים שיפור ביצועים מדיד: זמן תגובת מסד הנתונים יורד עד פי 10, LCP יורד ב-60%, ועלויות התחזוקה נחתכות בחצי. חיסכון משמעותי במשאבי ענן — מקרה אמיתי מאחד הלקוחות שלנו עם קטלוג של 50,000 מוצרים חסך $2000 בחודש. קבלו תוכנית שיפור תוך יום אחד — פשוט כתבו לנו.

הבעיות העיקריות שאנחנו פותרים

  • אופטימיזציית ביצועי Django ORM מבטלת שאילתות N+1: במקום 1+N שאילתות אנחנו עושים 2 (select_related/prefetch_related). זה יעיל פי 10-50 משאילתות ידניות.
  • אינדקסים חסרים: הוספת אינדקסים מורכבים לפילטרים תכופים.
  • לפחות שאילתה לא יעילה אחת בכל עמוד — annotations ו-aggregations ממזערים אותן.
  • אין חיבורים מתמשכים — כל חיבור נפתח מחדש, ומגדיל את זמן ההשהיה פי 5. חיבורים מתמשכים פותרים זאת, ומפחיתים את העיכוב עד פי 5 בהשוואה לפתיחת חיבור חדש לכל בקשה.
  • אין שכפול (replication) — מסד הנתונים הראשי עמוס. אנו מגדירים ניתוב קריאה אוטומטי ל-replica, ומפנים עומס מהראשי.

הגדרת חיבור PostgreSQL

ב-settings.py אנו מגדירים מספר מסדי נתונים אם יש צורך. חיבורים מתמשכים מפחיתים את זמן ההשהיה עד פי 5. טבלת הגדרות:

פרמטר ברירת מחדל replica
ENGINE django.db.backends.postgresql django.db.backends.postgresql
CONN_MAX_AGE 60 60
connect_timeout 10 —
TEST mirror — default
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': env('DB_NAME'),
        'USER': env('DB_USER'),
        'PASSWORD': env('DB_PASSWORD'),
        'HOST': env('DB_HOST', default='127.0.0.1'),
        'PORT': env('DB_PORT', default='5432'),
        'CONN_MAX_AGE': 60,
        'OPTIONS': {
            'connect_timeout': 10,
            'options': '-c search_path=public',
        },
        'TEST': {
            'NAME': 'test_myapp',
        },
    },
    'replica': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': env('DB_REPLICA_NAME'),
        'USER': env('DB_REPLICA_USER'),
        'PASSWORD': env('DB_REPLICA_PASSWORD'),
        'HOST': env('DB_REPLICA_HOST'),
        'PORT': '5432',
        'CONN_MAX_AGE': 60,
        'TEST': {
            'MIRROR': 'default',
        },
    },
}

למה מנהלים מותאמים אישית פותרים N+1?

מנהל מותאם אישית הוא הכלי המרכזי לאנקפסולציה של לוגיקת שאילתות. הוא טוען אוטומטית אובייקטים קשורים, ומבטל שאילתות N+1. בהשוואה לקריאה ידנית של DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': env('DB_NAME'), 'USER': env('DB_USER'), 'PASSWORD': env('DB_PASSWORD'), 'HOST': env('DB_HOST', default='127.0.0.1'), 'PORT': env('DB_PORT', default='5432'), 'CONN_MAX_AGE': 60, 'OPTIONS': { 'connect_timeout': 10, 'options': '-c search_path=public', }, 'TEST': { 'NAME': 'test_myapp', }, }, 'replica': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': env('DB_REPLICA_NAME'), 'USER': env('DB_REPLICA_USER'), 'PASSWORD': env('DB_REPLICA_PASSWORD'), 'HOST': env('DB_REPLICA_HOST'), 'PORT': '5432', 'CONN_MAX_AGE': 60, 'TEST': { 'MIRROR': 'default', }, }, } בכל view, מנהל מותאם אישית מפחית שאילתות פי 10-50 ומרכז את הלוגיקה.

שלבי יישום:

  1. הגדרת QuerySet עם מתודות לסינון וטעינה מוקדמת.
  2. יצירת מנהל שמחזיר QuerySet זה.
  3. שימוש במנהל בקוד — שרשראות מתודות קריאות וניתנות לתחזוקה.

דוגמה לקטלוג: מודלים Category ו-Product. הנה קוד המודל:

from django.db import models
from django.utils.text import slugify

class Category(models.Model):
    name = models.CharField(max_length=200)
    slug = models.SlugField(unique=True, max_length=220)
    parent = models.ForeignKey(
        'self',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='children',
    )

    class Meta:
        verbose_name_plural = 'categories'
        ordering = ['name']

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)

class Product(models.Model):
    class Status(models.TextChoices):
        DRAFT = 'draft', 'Draft'
        PUBLISHED = 'published', 'Published'
        ARCHIVED = 'archived', 'Archived'

    title = models.CharField(max_length=500)
    slug = models.SlugField(unique=True, max_length=520)
    category = models.ForeignKey(
        Category,
        on_delete=models.PROTECT,
        related_name='products',
    )
    price = models.DecimalField(max_digits=12, decimal_places=2)
    status = models.CharField(
        max_length=10,
        choices=Status.choices,
        default=Status.DRAFT,
    )
    tags = models.ManyToManyField('Tag', blank=True, related_name='products')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        indexes = [
            models.Index(fields=['status', '-created_at']),
            models.Index(fields=['category', 'status']),
        ]
איך מנהלים מותאמים אישית מבטלים שאילתות N+1 שלב אחר שלב
  1. שרשראות מתודות QuerySet יוצרות שאילתה אחת אופטימלית.
  2. select_related מחבר טבלאות ForeignKey בשאילתת SQL אחת.
  3. from django.db import models from django.utils.text import slugify class Category(models.Model): name = models.CharField(max_length=200) slug = models.SlugField(unique=True, max_length=220) parent = models.ForeignKey( 'self', null=True, blank=True, on_delete=models.SET_NULL, related_name='children', ) class Meta: verbose_name_plural = 'categories' ordering = ['name'] def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) super().save(*args, **kwargs) class Product(models.Model): class Status(models.TextChoices): DRAFT = 'draft', 'Draft' PUBLISHED = 'published', 'Published' ARCHIVED = 'archived', 'Archived' title = models.CharField(max_length=500) slug = models.SlugField(unique=True, max_length=520) category = models.ForeignKey( Category, on_delete=models.PROTECT, related_name='products', ) price = models.DecimalField(max_digits=12, decimal_places=2) status = models.CharField( max_length=10, choices=Status.choices, default=Status.DRAFT, ) tags = models.ManyToManyField('Tag', blank=True, related_name='products') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: indexes = [ models.Index(fields=['status', '-created_at']), models.Index(fields=['category', 'status']), ] מצמצם חיפושי ManyToMany לשתי שאילתות.
  4. המנהל מבטיח שכל view משתמש במתודות אלה כברירת מחדל.

אופטימיזציית שאילתות עם Annotations

Annotations וביטויי F מאפשרים aggregation ועדכון בשאילתה אחת ללא מעבר הלוך-חזור ב-Python:

class PublishedProductQuerySet(models.QuerySet):
    def published(self):
        return self.filter(status=Product.Status.PUBLISHED)

    def with_category(self):
        return self.select_related('category')

    def with_tags(self):
        return self.prefetch_related('tags')

    def in_price_range(self, min_price, max_price):
        return self.filter(price__gte=min_price, price__lte=max_price)


class ProductManager(models.Manager):
    def get_queryset(self):
        return PublishedProductQuerySet(self.model, using=self._db)

    def published(self):
        return self.get_queryset().published()


# Использование:
products = (
    Product.objects.published()
    .with_category()
    .with_tags()
    .in_price_range(100, 5000)
    .order_by('-created_at')[:20]
)

גישה זו מפחיתה את מספר השאילתות מעשרות לאחת, ומשפיעה ישירות על TTFB.

איך מגדירים שכפול עם ניתוב אוטומטי?

שכפול מסד נתונים קורא מ-replica, כתיבות הולכות לראשי. זה מפנה עומס ממסד הנתונים הראשי ומגביר סובלנות לתקלות. הנה router פשוט:

from django.db.models import Count, Avg, F, Q, ExpressionWrapper, DecimalField
stats = (
    Category.objects
    .annotate(
        product_count=Count('products', filter=Q(products__status='published')),
        avg_price=Avg('products__price', filter=Q(products__status='published')),
    )
    .filter(product_count__gt=0)
    .order_by('-product_count')
)
Product.objects.filter(status='published').update(
    price=ExpressionWrapper(F('price') * 1.1, output_field=DecimalField())
)

שלבי הגדרה:

  1. יצירת replica של PostgreSQL (פיזית או לוגית).
  2. הגדרת מסדי נתונים ב-class PublishedProductQuerySet(models.QuerySet): def published(self): return self.filter(status=Product.Status.PUBLISHED) def with_category(self): return self.select_related('category') def with_tags(self): return self.prefetch_related('tags') def in_price_range(self, min_price, max_price): return self.filter(price__gte=min_price, price__lte=max_price) class ProductManager(models.Manager): def get_queryset(self): return PublishedProductQuerySet(self.model, using=self._db) def published(self): return self.get_queryset().published() # Использование: products = ( Product.objects.published() .with_category() .with_tags() .in_price_range(100, 5000) .order_by('-created_at')[:20] ) .
  3. יישום ה-router כפי שמוצג למעלה.
  4. הפעלת ה-router ב-from django.db.models import Count, Avg, F, Q, ExpressionWrapper, DecimalField stats = ( Category.objects .annotate( product_count=Count('products', filter=Q(products__status='published')), avg_price=Avg('products__price', filter=Q(products__status='published')), ) .filter(product_count__gt=0) .order_by('-product_count') ) Product.objects.filter(status='published').update( price=ExpressionWrapper(F('price') * 1.1, output_field=DecimalField()) ) .

כללי מיגרציה בסביבת ייצור

  • הוספת עמודה nullable אינה נועלת את הטבלה ב-PostgreSQL 11+.
  • יצירת אינדקסים באמצעות class ReadReplicaRouter: READ_DB = 'replica' WRITE_DB = 'default' def db_for_read(self, model, **hints): return self.READ_DB def db_for_write(self, model, **hints): return self.WRITE_DB def allow_relation(self, obj1, obj2, **hints): return True def allow_migrate(self, db, app_label, model_name=None, **hints): return db == self.WRITE_DB # settings.py DATABASE_ROUTERS = ['myapp.db_router.ReadReplicaRouter'] — Django משתמש בזה אוטומטית עבור PostgreSQL, מה שלא נועל את הטבלה במהלך יצירת האינדקס. זה חשוב לסביבת ייצור.
  • שינוי שם עמודה: בשני שלבים (הוספת חדשה → העתקת נתונים → הסרת ישנה).
  • DATABASES — רק לסנכרון מצב ללא הרצת SQL מחדש.

מה כלול בהגדרת Django ORM Turnkey?

שלב תיאור משך
ביקורת סכמה נוכחית זיהוי N+1, שאילתות כפולות, אינדקסים חסרים יום אחד
עיצוב הגדרת אסטרטגיית אינדקסים, שכפול, מנהלים חצי יום
יישום הגדרת חיבורים, מודלים, מנהלים, router 1–2 ימים
בדיקות בדיקות עומס, אימות ביצועים חצי יום
תיעוד והדרכה דיאגרמת ER, תיאור QuerySet, המלצות חצי יום

תוצרים:

  • פרטי גישה למסד הנתונים (משתמשי קריאה/כתיבה, נקודות קצה של replica)
  • דיאגרמת ER מלאה ומתועדת (מודל ישויות-קשרים)
  • צוות שהוכשר על שימוש ב-QuerySet ועקרונות שכפול
  • שבועיים של תמיכה לאחר ההשקה (Slack/אימייל)

כדי לשפר את ביצועי Django ORM, צרו קשר כדי להעריך את הפרויקט שלכם ולהציע תוכנית פעולה. קבלו ייעוץ על אופטימיזציית Django — נענה על כל השאלות. התחילו בהזמנת ביקורת על הסכמה הנוכחית שלכם: ננתח שאילתות, אינדקסים וחיבורים, ואז נספק דוח מפורט עם המלצות.