Wprowadzenie
Django i PostgreSQL to jedno z najbardziej dojrzałych i stabilnych połączeń w świecie backendu Python. Jednak przy milionach rekordów, dużym ruchu i skomplikowanych zapytaniach domyślna konfiguracja przestaje wystarczać.
W tym artykule pokazujemy:
- jak optymalizować ORM Django,
- jak projektować indeksy w PostgreSQL,
- jak analizować zapytania przy pomocy
EXPLAIN ANALYZE,
- kiedy stosować partycjonowanie,
- checklistę produkcyjną dla projektów high-scale.
Oficjalne dokumentacje:
1. Najczęstsze problemy przy dużych danych
W projektach R&D najczęściej spotykamy:
- N+1 queries
- brak indeksów
- nieoptymalne
JOIN
- sortowanie bez indeksu
- filtrowanie po polach tekstowych bez wsparcia full-text
2. Eliminacja N+1 – select_related i prefetch_related
Problem
orders = Order.objects.all()
for order in orders:
print(order.client.name)
To generuje osobne zapytanie dla każdego klienta.
Rozwiązanie
orders = Order.objects.select_related("client")
for order in orders:
print(order.client.name)
Dla relacji ManyToMany:
orders = Order.objects.prefetch_related("products")
Efekt: mniej zapytań, niższa latencja, mniejsze obciążenie DB.
3. Indeksy – fundament wydajności
Brak indeksu przy milionach rekordów oznacza pełny scan tabeli.
Indeks w Django
from django.db import models
class Order(models.Model):
created_at = models.DateTimeField(db_index=True)
status = models.CharField(max_length=50)
class Meta:
indexes = [
models.Index(fields=["status", "created_at"]),
]
Indeks w PostgreSQL
CREATE INDEX idx_orders_status_created
ON orders (status, created_at);
Zasada:
- indeksujemy pola używane w WHERE
- indeksujemy pola używane w ORDER BY
- unikamy nadmiaru indeksów
4. EXPLAIN ANALYZE – analiza zapytań
W PostgreSQL:
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE status = 'PAID'
ORDER BY created_at DESC;
Sprawdzamy: czy używany jest indeks, koszt zapytania, czas wykonania.
Ciekawostka: czasami dodanie indeksu pogarsza wydajność, jeśli planner uzna go za nieopłacalny.
5. Agregacje i annotate – optymalizacja ORM
from django.db.models import Count
top_clients = (
Order.objects
.values("client_id")
.annotate(total=Count("id"))
.order_by("-total")
)
Dobre praktyki:
- ograniczaj
values() do potrzebnych pól
- unikaj
.all() bez filtrów
- stosuj paginację
6. Paginacja – klucz przy dużych tabelach
Nie używaj:
Order.objects.all()[0:100000]
Używaj:
from django.core.paginator import Paginator
paginator = Paginator(Order.objects.all(), 100)
page = paginator.get_page(1)
Jeszcze lepiej przy dużych danych: keyset pagination:
Order.objects.filter(id__gt=last_id).order_by("id")[:100]
7. Partycjonowanie PostgreSQL
Przy dziesiątkach milionów rekordów warto rozważyć partycjonowanie.
Przykład partycjonowania po dacie:
CREATE TABLE orders (
id serial,
created_at date,
status text
) PARTITION BY RANGE (created_at);
Korzyści: szybsze zapytania zakresowe, łatwiejsze archiwizowanie danych, lepsza organizacja tabel.
8. Full-Text Search
Zamiast LIKE '%fraza%':
CREATE INDEX idx_search
ON products
USING GIN(to_tsvector('english', description));
W Django:
from django.contrib.postgres.search import SearchVector
Product.objects.annotate(
search=SearchVector("description")
).filter(search="laptop")
9. Cache – niedoceniany element
Przy dużym ruchu cache bywa ważniejszy niż sama baza.
Opcje: Redis, Memcached, Django cache framework.
Przykład:
from django.views.decorators.cache import cache_page
@cache_page(60 * 5)
def product_list(request):
...
10. Checklist produkcyjna
ORM
- [ ] select_related używany poprawnie
- [ ] brak N+1
- [ ] paginacja wdrożona
PostgreSQL
- [ ] indeksy na kluczowych kolumnach
- [ ] regularne VACUUM i ANALYZE
- [ ] monitoring slow queries
- [ ] EXPLAIN ANALYZE dla kluczowych zapytań
Architektura
- [ ] cache warstwowy
- [ ] read replicas przy dużym ruchu
- [ ] connection pooling (np. PgBouncer)
★
Ciekawostka
80% problemów wydajnościowych w Django wynika z nieoptymalnego ORM, nie z samej bazy.
11. Ciekawostki
- PostgreSQL posiada zaawansowany planner zapytań, który dynamicznie wybiera najlepszą strategię.
- Indeks GIN jest szczególnie efektywny przy wyszukiwaniu tekstowym.
Podsumowanie
Django + PostgreSQL to bardzo skalowalne połączenie – pod warunkiem świadomej architektury.
Kluczem nie jest „więcej serwerów”, lecz:
- poprawne indeksowanie,
- analiza zapytań,
- optymalizacja ORM,
- cache i partycjonowanie.
W projektach R&D optymalizacja powinna być procesem ciągłym, a nie jednorazowym działaniem.