Django w enterprise po DEP 20: koniec LTS, roczny cykl wydań i co to zmienia w planowaniu migracji
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:
| Wydanie | Data | Koniec wsparcia podstawowego | Koniec wsparcia rozszerzonego |
|---|---|---|---|
| Django 6.1 | sierpień 2026 | kwiecień 2027 | grudzień 2027 |
| Django 6.2 LTS | kwiecień 2027 | grudzień 2027 | kwiecień 2030 |
| Django 2028 | styczeń 2028 | styczeń 2029 | styczeń 2031 |
| Django 2029 | styczeń 2029 | styczeń 2030 | styczeń 2032 |
| Django 2030 | styczeń 2030 | styczeń 2031 | styczeń 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— wykonujeSQL JOINw jednym zapytaniu; dlaOneToOneFieldiForeignKey.prefetch_related— osobne zapytania dlaManyToManyFieldi odwrotnych relacjiForeignKey, łą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
| Cecha | Django | Flask |
|---|---|---|
| Typ | Pełny framework | Mikro-framework |
| Baza danych | Wbudowany ORM + migracje | SQLAlchemy, Alembic |
| Panel admina | Generowany automatycznie | Brak (Flask-Admin) |
| Uwierzytelnianie | Wbudowane | Brak (Flask-Login, Flask-Security-Too) |
| REST API | DRF | Ręcznie lub Flask-RESTful |
| Zabezpieczenia | Domyślnie włączone | Konfiguracja ręczna |
| Zastosowanie | Duże aplikacje transakcyjne, CMS, e-commerce | Lekkie 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
- 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.
- 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.
- 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.
- 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.