惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

宝玉的分享
宝玉的分享
H
Hackread – Cybersecurity News, Data Breaches, AI and More
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
小众软件
小众软件
月光博客
月光博客
D
DataBreaches.Net
L
LangChain Blog
美团技术团队
S
SegmentFault 最新的问题
MyScale Blog
MyScale Blog
大猫的无限游戏
大猫的无限游戏
博客园 - 司徒正美
aimingoo的专栏
aimingoo的专栏
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
H
Help Net Security
阮一峰的网络日志
阮一峰的网络日志
Y
Y Combinator Blog
I
InfoQ
U
Unit 42
Microsoft Azure Blog
Microsoft Azure Blog
J
Java Code Geeks
博客园 - 三生石上(FineUI控件)
腾讯CDC
Martin Fowler
Martin Fowler

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Understanding Memory Usage in Django Webserver Workers
Aidas Bendoraitis · 2026-06-14 · via DEV Community

If you are coming from the PHP world, you might be used to thinking that when a request reaches the web server, everything is parsed and processed from scratch. In Python, however, the behavior is a little different.

A Python web server (for example, Gunicorn) starts one or more worker processes and then continuously accepts and processes requests as a running server application. In this article, I will explore how memory is managed in that environment.

Startup time vs. request execution

When you run a Django web server (either the development server or a Gunicorn-based deployment), there are two phases of execution: startup, when the server application is initialized, and the request/response cycle, when a request is received and processed to return a response.

During startup, wsgi.py and get_wsgi_application() are executed once per worker at boot time, Django settings are evaluated, applications listed in INSTALLED_APPS are imported and registered, ready() methods of app configs are called, and URL patterns are compiled.

During request execution, the middleware chain processes the request, the URL is resolved, the view is executed, context managers run within the view, the template is rendered, the middleware chain processes the response, and the response is returned.

The Django development server runs a single worker process but uses threads to handle concurrent requests. Gunicorn runs multiple worker processes (separate copies of the server application). A common rule of thumb for Gunicorn configuration is (2 × number_of_CPU_cores) + 1 workers.

Little demo of shared memory between requests

If you have mutable globals in your Django code, they will be shared within the same worker process, even across threads.

Here's a simple example for demonstration purposes (note that this is an antipattern and should never be used in production):

from django.http import JsonResponse
from django.utils.timezone import now

TIMESTAMPS = []

def show_timestamps(request):
    TIMESTAMPS.append(now())
    return JsonResponse({"timestamps": TIMESTAMPS})

The output would be (manually indented for readability):

{"timestamps": [
  "2026-06-08T19:48:42.044Z", 
  "2026-06-08T19:48:43.875Z", 
  "2026-06-08T19:48:44.776Z"
]}

If you serve this application with Gunicorn using multiple workers and open the view in several tabs, refreshing them a few times, you will see that the timestamp list keeps growing. However, because each request may be served by a different worker, the timestamps returned by each worker will differ.

How RAM is managed

1. Compilation/setup state is persistent

When Django starts and the application server is initialized, the following objects live in process memory for the lifetime of the worker process:

  • django.apps.registry.Apps (the app registry with all models)
  • URL resolvers (urlpatterns)
  • Middleware instances (if instantiated at startup)
  • Cached template engines
  • Database connection configurations (not connections themselves)

This memory normally remains allocated until the worker process restarts.

2. Execution state lives per-request

Each request creates objects that are local to that request's call stack—views, forms, querysets, serializers, and so on. Once the response is sent:

  • Python's garbage collector normally reclaims those objects.
  • There is no explicit Django cleanup; Django relies on Python's normal memory management.
  • Request-scoped objects are cleaned up only after they go out of scope and no references to them remain.

Common bugs to avoid

1. Mutable module-level state

A mutable object defined at the module level persists across all requests handled by that worker process and will accumulate data from every user.

BAD:

_recent_users = []

def dashboard(request):
    # leaks across requests!
    _recent_users.append(request.user.id)
    return render(
        request, 
        "dashboard.html", 
        {"recent": _recent_users}
    )

GOOD — state belongs in the DB, cache, or session:

def dashboard(request):
    recent_users = get_recent_users_from_db()
    return render(
        request, 
        "dashboard.html", 
        {"recent": recent_users}
    )

2. Storing per-request data on a shared instance

Middleware instances live for the lifetime of the worker process. Storing anything request-specific on self means it will be shared across every request handled by that worker.

BAD:

class AuditMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # shared across all requests!
        self.current_user = None

    def __call__(self, request):
        # User A's data visible when User B's request runs
        self.current_user = request.user
        return self.get_response(request)

GOOD — attach data to the request object, which is scoped to a single request:

class AuditMiddleware:
    def __init__(self, get_response):
        # only immutable config on self
        self.get_response = get_response

    def __call__(self, request):
        # scoped to this request's lifetime
        request.current_user = request.user
        return self.get_response(request)

3. Class-level cache shared across instances

A class attribute is shared across all instances of that class and therefore across all requests. The first user's data may be cached and returned to everyone else.

BAD:

class ReportGenerator:
    # class attribute, shared across all instances and all requests!
    _cache = {}

    def get_data(self, user):
        if "data" not in self._cache:
            # first user's data cached for everyone
            self._cache["data"] = expensive_query(user)  
        return self._cache["data"]

GOOD — use the cache framework with a user-scoped key:

from django.core.cache import cache

class ReportGenerator:
    def get_data(self, user):
        key = f"report:{user.id}"
        return cache.get_or_set(
            key, 
            lambda: expensive_query(user), 
            timeout=300,
        )

Mental model

Before writing module-level code, ask yourself:

"If 1000 different users hit this worker, and this variable exists for all of them, is that safe?"

Location Lifetime Safe for user data?
Local variable in a view/function Single request ✅ Yes
request object attributes Single request ✅ Yes
threading.local() Single thread ✅ Yes
Database / cache with scoped keys Persistent but keyed ✅ Yes
self on a middleware/class instance Worker lifetime ⚠️ Only immutable config
Module-level mutable variable Worker lifetime ❌ Never user data
Class-level mutable attribute Worker lifetime ❌ Never user data

Rule of thumb: immutable configuration (numbers, booleans, strings, bytes, tuples, frozensets, and None) belongs at module level; anything that varies by request, user, or time belongs in the request cycle.

Conclusion

A Django worker process has two separate memory areas: a startup state that lives for the lifetime of the worker process (app registry, URL resolvers, middleware instances, template engines) and a request state that exists only while handling a request (views, querysets, forms) and is reclaimed by Python's garbage collector afterward.

The main rule is that immutable configuration can live at the module level, but anything that depends on the current user or request should stay inside the request cycle, such as local variables or the request object.

That's because module-level variables, class attributes, and middleware instance state are shared by all requests handled by the same worker, making them unsafe for user-specific data and a common cause of memory leaks and data exposure bugs.


Cover Picture by Jon Tyson