Przejdź do głównej zawartości

Django w enterprise po DEP 20: koniec LTS, roczny cykl wydań i co to zmienia w planowaniu migracji

· 8 min aby przeczytać
Przemysław Majdak
Full-Stack Developer, Automation Engineer & Web Security Specialist

Django Software Foundation przyjęła DEP 20: od stycznia 2028 framework przechodzi na jedno wydanie funkcjonalne rocznie, z numeracją kalendarzową (Django 2028, Django 2029), a etykieta „LTS" zostaje wycofana — bo każde wydanie dostaje ten sam, trzyletni okres wsparcia. Dla zespołów utrzymujących systemy produkcyjne to najważniejsza zmiana w planowaniu budżetu utrzymania od lat: znika przepaść między wersjami LTS, przez którą migracje robiło się skokowo, po dwa lata zmian naraz. Poniżej co dokładnie się zmienia, jak wygląda tabela przejściowa i dlaczego akurat teraz warto przejrzeć swój plan aktualizacji.

Co dokładnie przyjął Steering Council

DEP 20 („Annual Release Cycle", autor Carlton Gibson, status Accepted) zmienia dwie rzeczy naraz: rytm wydań i sposób ich wspierania (djangoproject.com, tekst DEP na GitHubie).

Dotychczasowy cykl ośmiomiesięczny ustępuje miejsca jednemu wydaniu funkcjonalnemu rocznie, w styczniu. Numer wersji to odtąd rok publikacji: 2028.0 to wydanie ze stycznia 2028, 2028.1 pierwsze wydanie łatające po nim, 2029.0 kolejne wydanie funkcjonalne.

Każde wydanie dostaje trzy lata wsparcia: rok poprawek błędów (mainstream), potem dwa lata poprawek bezpieczeństwa i ochrony przed utratą danych. To dokładnie tyle, ile dotąd dostawały wyłącznie wersje LTS — dlatego sama etykieta „LTS" z DEP 4 zostaje wycofana. W każdym momencie wspierane są trzy wydania: bieżące plus dwa poprzednie roczniki.

Polityka deprecacji i stabilności API się nie zmienia. W ujęciu kalendarzowym okresy deprecacji wręcz się wydłużają.

Tabela przejściowa — daty, które trafiają do harmonogramu

Nic nie zmienia się przed 2028 rokiem. Zobowiązania wsparcia dla Django 5.2 LTS i 6.2 LTS zostają takie, jak je ogłoszono. Okres przejściowy wygląda tak:

WydanieDataKoniec wsparcia podstawowegoKoniec wsparcia rozszerzonego
Django 6.1sierpień 2026kwiecień 2027grudzień 2027
Django 6.2 LTSkwiecień 2027grudzień 2027kwiecień 2030
Django 2028styczeń 2028styczeń 2029styczeń 2031
Django 2029styczeń 2029styczeń 2030styczeń 2032
Django 2030styczeń 2030styczeń 2031styczeń 2033

Praktyczny wniosek dla systemów produkcyjnych: Django 6.2 LTS z kwietnia 2027 to ostatnia wersja w starym systemie i jednocześnie ta z najdłuższym oknem — wsparcie rozszerzone do kwietnia 2030. Jeśli planujesz jedną większą migrację i potem spokój na kilka lat, to jest ten cel. Daje trzy lata na dostosowanie infrastruktury do numeracji kalendarzowej, bez presji przeskakiwania od razu na 2028.

Słaby punkt tego planu: wchodząc na 6.2 LTS i siedząc na niej do 2030, wracasz dokładnie do problemu, który DEP 20 miał rozwiązać — kumulacji trzech roczników zmian do nadrobienia za jednym razem. Sens nowego cyklu polega na aktualizacji o jeden rocznik rocznie. 6.2 LTS jest bezpiecznym przystankiem, nie strategią docelową.

Osobna korzyść, która w praktyce zdejmuje sporo bólu z macierzy zależności: Python wydaje się co roku w październiku, a Django dotąd tego rytmu nie łapało. Od 2028 każda wersja Django wspiera trzy najnowsze wersje Pythona w momencie premiery i dobiera kolejną w pierwszym roku życia. Koniec z wersjami LTS ciągnącymi macierz Pythonów długo po ich własnym końcu wsparcia.

Skalowanie: liczby z produkcji, nie z landing page'a

Przy dyskusji „czy Django uciągnie" najczęściej pada argument z Instagrama — zwykle w wersji tak ogólnej, że nic nie wnosi. Konkret jest ciekawszy niż hasło.

Serwer Instagrama działa na Django w trybie wieloprocesowym: proces nadrzędny forkuje kilkadziesiąt workerów, uWSGI w trybie pre-fork ma dzielić pamięć między nimi. Problem w tym, że garbage collector Pythona rozwalał współdzielenie pamięci copy-on-write — samo przechodzenie GC po obiektach dotykało stron pamięci i wymuszało ich kopiowanie. Po wyłączeniu GC pamięć współdzielona wzrosła ze 140 MB do 225 MB na worker, całkowite zużycie pamięci spadło o 8 GB na maszynę, co dało około 25% RAM-u dla całej floty Django i mniej więcej 10% ogólnej efektywności (Instagram Engineering).

Techniczny detal, który jest tu najbardziej użyteczny: zespół użył gc.set_threshold(0), a nie gc.disable() — bo set_threshold(0) jest odporne na późniejsze wywołania gc.enable() gdzieś w zależnościach.

Drugi przypadek wart znajomości to Disqus, który wyskalował pojedynczą aplikację Django do około 8 miliardów odsłon miesięcznie przy 45 tys. żądań na sekundę. Kluczowa liczba nie jest jednak w Django: z tych 45 tys. żądań do serwerów aplikacyjnych docierało tylko około 15 tys., resztę zdejmował Varnish (blog.disqus.com).

To jest właściwy morał obu historii i on jest mało efektowny: architektura bezstanowa skaluje się horyzontalnie bez dramatów, a realne wąskie gardła leżą w warstwie cache'ującej i w komunikacji z bazą, nie w samym frameworku.

Czego Django nie napędza

Listy „firmy używające Django" krążące po blogach i materiałach marketingowych regularnie dopisują sobie nazwiska, które się nie zgadzają. Warto to mieć uporządkowane, bo argument „skoro X na tym działa" bywa używany w decyzjach architektonicznych:

  • Spotify — backend oparty na architekturze asynchronicznej (Twisted, później gevent), nie na Django.
  • Reddit — droga od Common Lisp przez web.py do Pylons; Django nigdy.
  • YouTube — powstał przed popularyzacją Django i nigdy go nie wdrożył.
  • Uber — dla usług pythonowych stosuje Flask.
  • Dropbox — historycznie Pyramid i rozwiązania własne.

Potwierdzone wdrożenia produkcyjne to Instagram, Disqus, Sentry (cały system jest aplikacją Django, a wersja komercyjna rozszerza open-source przez sygnały), Bitbucket Cloud, Eventbrite czy Nextdoor. To wciąż mocna lista — po prostu krótsza niż ta reklamowa.

ORM: dwie metody, które decydują o wydajności

Django ORM generuje migracje wersjonowane w Gicie i daje spójność referencyjną za darmo, ale wydajność zależy od świadomego pisania zapytań. Cały ciężar spoczywa na dwóch metodach pobierania wyprzedzającego:

  • select_related — wykonuje SQL JOIN w jednym zapytaniu; dla OneToOneField i ForeignKey.
  • prefetch_related — osobne zapytania dla ManyToManyField i odwrotnych relacji ForeignKey, łączone w pamięci Pythona; unika kosztownych złączeń na dużych tabelach pośrednich.

Poza tym trzy rzeczy, które w praktyce najczęściej ratują pamięć i CPU:

# rekordy partiami zamiast całego QuerySetu w RAM
for obj in Model.objects.iterator():
...

# słowniki/krotki zamiast pełnych instancji modeli
Model.objects.values("id", "name")

# modyfikacje po stronie bazy, bez instancjonowania obiektów
Model.objects.filter(active=False).update(status="archived")

# spójność operacji — obowiązkowo tam, gdzie idą pieniądze
with transaction.atomic():
...

Do tego db_index=True na kolumnach używanych w WHERE, ORDER BY i złączeniach, oraz filter().first() zamiast get() tam, gdzie brak rekordu jest normalnym scenariuszem — zwraca None zamiast rzucać ObjectDoesNotExist.

Warto tu zaznaczyć jedno: select_related i prefetch_related to rozwiązanie problemu N+1 na poziomie ORM-a, ale sam problem nie jest specyfiką Django ani nawet ORM-ów. Pisałem o tym szerzej w Batching: jedno zapytanie zamiast pięciuset — koszt siedzi w round-tripach sieciowych, nie w bazie.

DRF i to, co dostajesz z pudełka

Django REST Framework dokłada serializatory (z ModelSerializer generowanym z modelu), klasy uprawnień (IsAuthenticated, IsAdminUser), throttling, uwierzytelnianie sesyjne, tokenowe, JWT przez djangorestframework-simplejwt oraz OAuth2, a drf-yasg generuje dokumentację Swagger/OpenAPI.

Sam framework domyślnie chroni przed CSRF, XSS, SQL Injection i clickjackingiem oraz dostarcza haszowanie haseł i zarządzanie sesjami. To jest realna przewaga nad składaniem stosu samodzielnie — nie dlatego, że tych zabezpieczeń nie da się dodać do Flaska, tylko dlatego, że w Django nikt nie musi o nich pamiętać.

Dwie rzeczy, o których trzeba pamiętać mimo wszystko: paginacja (PageNumberPagination albo LimitOffsetPagination ustawione globalnie — bez tego pierwsze duże zapytanie kolekcyjne kładzie aplikację) i APITestCase do testów integracyjnych w CI.

Django czy Flask — kryterium, które faktycznie działa

CechaDjangoFlask
TypPełny frameworkMikro-framework
Baza danychWbudowany ORM + migracjeSQLAlchemy, Alembic
Panel adminaGenerowany automatycznieBrak (Flask-Admin)
UwierzytelnianieWbudowaneBrak (Flask-Login, Flask-Security-Too)
REST APIDRFRęcznie lub Flask-RESTful
ZabezpieczeniaDomyślnie włączoneKonfiguracja ręczna
ZastosowanieDuże aplikacje transakcyjne, CMS, e-commerceLekkie mikroserwisy, API o niestandardowej strukturze

Rozstrzygające kryterium nie jest techniczne, tylko organizacyjne. Django eliminuje zmęczenie decyzyjne — ujednolicony układ projektu oznacza, że nowy programista w zespole wie, gdzie co leży, bez czytania cudzych wyborów architektonicznych. Flask daje pełną kontrolę nad stosem, co jest zaletą przy wąsko wyspecjalizowanej usłudze i kosztem przy każdym kolejnym module dokładanym do rosnącej aplikacji.

Słaby punkt Django: ten sam mechanizm, który skraca wdrożenie, potrafi przeszkadzać, gdy potrzebujesz czegoś poza konwencją. Wtedy płacisz za walkę z frameworkiem zamiast za jego wykorzystanie — i to jest moment, w którym mikro-framework wygrywa.

Co z tego wynika operacyjnie

  1. Zaplanuj migrację na Django 6.2 LTS (kwiecień 2027, wsparcie do kwietnia 2030) jako bezpieczny przystanek — ale wpisz do harmonogramu przejście na cykl roczny, zamiast dojechać do 2030 i nadrabiać wszystko naraz.
  2. Przejrzyj macierz Pythona — od 2028 okno wsparcia Django kończy się razem z najstarszym wspieranym Pythonem, więc stare wersje interpretera przestaną być ciągnięte latami przez LTS-y.
  3. Zadania asynchroniczne wypchnij do Celery, statyki do WhiteNoise albo magazynu obiektowego — to nadal domyślna, nudna i działająca odpowiedź na krótki czas odpowiedzi HTTP.
  4. Zanim zaczniesz optymalizować Django, sprawdź warstwę wyżej. Disqus przepuszczał do aplikacji jedną trzecią ruchu. Instagram zyskał 10% na ustawieniu jednego progu GC. Ani jedno, ani drugie nie było zmianą w kodzie aplikacyjnym.