SSD Nodes Learn 🎉 VPS ab $5.50/Monat
Anleitungen Matt ConnorVon Matt Connor

Django vs Flask auf einem kleinen VPS: RAM und Worker

Wie viel RAM kosten Django und Flask auf einem VPS mit 1 oder 2 GB? Vergleichen Sie Speicher pro gunicorn-Worker und die realistisch mögliche Worker-Anzahl.

Kosten von Django und Flask auf einem kleinen VPS

Bei Django und Flask auf einem kleinen VPS ist der Speicherverbrauch zuerst entscheidend. Django lädt seinen Object-Relational Mapper (ORM), seine Migrationsmechanismen und, sofern aktiviert, die Administrationsoberfläche in jeden gestarteten Worker-Prozess. Flask lädt einen Router und ein Request-Objekt. Auf einem System mit 1 GB bestimmt dieser Unterschied, wie viele Worker Platz haben. Die Anzahl der Worker bestimmt wiederum, wie viele Requests Sie gleichzeitig verarbeiten können.

Diese Kosten fallen bei Django nur dann ins Gewicht, wenn Sie die bereitgestellten Funktionen nicht selbst neu implementieren. Eine Anwendung mit Benutzerkonten, Sessions und einem Administrationsbereich ist ein Fall für Django: Der RAM-Verbrauch pro Worker ist der Preis für Code, den Sie nicht selbst schreiben müssen. Eine JSON-API vor einem bereits betriebenen Datenspeicher ist ein Fall für Flask, weil die zusätzlichen Funktionen von Django dort ohnehin nicht geladen würden. Es geht um die passende Technologie. Die folgenden Messungen zeigen, welcher Fall auf Ihre Anwendung zutrifft.

Wie viel Arbeitsspeicher verwendet ein gunicorn-Worker?

ChartMemory per gunicorn worker, minimal app, three workers with preload on
The data behind this chart
[
  {
    "label": "Bare Python 3.12 process",
    "rss_mb": 14,
    "pss_mb": 9
  },
  {
    "label": "Flask, one route",
    "rss_mb": 42,
    "pss_mb": 26
  },
  {
    "label": "Flask + SQLAlchemy",
    "rss_mb": 58,
    "pss_mb": 38
  },
  {
    "label": "Django, admin disabled",
    "rss_mb": 78,
    "pss_mb": 47
  },
  {
    "label": "Django, admin enabled",
    "rss_mb": 96,
    "pss_mb": 58
  }
]

Das sind typische veröffentlichte Werte für eine Hello-World-Anwendung der jeweiligen Art unter Ubuntu 24.04 mit Python 3.12, drei gunicorn-Workern und aktiviertem Preload. Betrachten Sie sie als Untergrenze, da Ihre eigenen Imports zusätzlich Speicher benötigen. Ein Django-Worker mit aktiviertem Admin belegt 96 MB residenten Speicher, während sein proportionaler Speicheranteil 58 MB beträgt. Der Unterschied zwischen diesen beiden Werten ist das Thema des nächsten Abschnitts.

Führen Sie dieselbe Messung auf Ihrem eigenen System durch.

sudo apt update && sudo apt install -y python3-venv
python3 -m venv /srv/site1/.venv
/srv/site1/.venv/bin/pip install django gunicorn setproctitle

Installieren Sie setproctitle. Wenn es vorhanden ist, benennt gunicorn seine Prozesse in gunicorn: master [site1] und gunicorn: worker [site1] um. Dadurch können die nächsten Befehle die Worker anhand ihres Namens finden, statt den Namen erraten zu müssen.

pgrep -af gunicorn
ps -o pid,rss,args -p $(pgrep -d, -f 'gunicorn: worker')

Die Spalte rss enthält die Resident Set Size in Kilobyte: jede Speicherseite, die der Prozess derzeit im RAM hält. Wenn Sie diesen Wert über alle Worker addieren, ist das Ergebnis zu hoch. Ein geforkter Worker teilt Speicherseiten mit seinem Parent-Prozess und seinen Geschwistern. Daher wird dieselbe Seite mehrfach gezählt. Fragen Sie stattdessen den Kernel nach der Proportional Set Size (PSS). Sie verteilt jede gemeinsam genutzte Seite auf die Prozesse, die sie abbilden.

for pid in $(pgrep -f 'gunicorn: worker'); do awk -v p="$pid" '/^Pss:/ {printf "%s  %d MB\n", p, $2/1024}' /proc/$pid/smaps_rollup; done

Führen Sie den Befehl als Benutzer aus, dem die Worker gehören, oder mit sudo. Für die Speicherplanung ist PSS die relevante Spalte, weil PSS korrekt summiert wird und RSS nicht.

Django ist größer, weil django.setup() bestimmte Aufgaben ausführt. Es importiert jeden Eintrag in INSTALLED_APPS, erstellt die Anwendungsregistrierung und instanziiert jede Model-Klasse zusammen mit einem Python-Objekt für jedes ihrer Felder. Das Hinzufügen von django.contrib.admin führt die automatische Admin-Erkennung aus. Dabei wird das admin-Modul jeder Anwendung importiert, wodurch zusätzlich die Formular- und Template-Schichten geladen werden. Ein Flask-Worker importiert Werkzeug und Jinja2 und beendet den Ladevorgang dann.

Ein wichtiger Vorbehalt: Das Framework ist häufig nur der kleinere Teil. Ein Worker, der ein Cloud-SDK oder numerische Bibliotheken importiert, benötigt dafür oft mehr Speicher als für Django. Messen Sie Ihre tatsächliche Anwendung, bevor Sie das Framework als Ursache festlegen.

Copy-on-Write und warum Preload die Anzahl verändert

Der Master-Prozess von Gunicorn forkt die Worker. Direkt nach fork() teilt der Child-Prozess jede Speicherseite mit dem Parent-Prozess. Der Kernel kopiert eine Seite erst, wenn eine der beiden Seiten darauf schreibt. Ob die Django-Model-Registry auf dem Server einmal oder viermal vorhanden ist, hängt daher davon ab, in welchem Prozess sie vor oder nach dem Fork erstellt wurde.

Wenn preload_app deaktiviert ist, importiert jeder Worker die Anwendung nach dem Fork. Dadurch erstellt jeder Worker eine eigene private Kopie. Ist die Option aktiviert, importiert der Master die Anwendung einmal, und die Worker übernehmen diese Speicherseiten.

import gc

bind = "unix:/run/site1/gunicorn.sock"
umask = 0o007
workers = 3
timeout = 30
preload_app = True
max_requests = 500
max_requests_jitter = 50

def when_ready(server):
    gc.freeze()

CPython wirkt dem Copy-on-Write-Verfahren entgegen. Jeder Objekt-Header enthält einen Referenzzähler. Der Zugriff auf ein Objekt schreibt daher in diesen Header. Beim Durchlaufen des Heaps durch den Garbage Collector werden gemeinsam genutzte Seiten deshalb nach und nach kopiert. gc.freeze() verschiebt alle bisher allozierten Objekte in eine permanente Generation, die der Garbage Collector nicht mehr besucht. Dadurch bleiben mehr dieser Seiten gemeinsam genutzt. when_ready ist der richtige Hook, weil er nach dem Preload und vor dem Fork des ersten Workers ausgeführt wird. Messen Sie die PSS vor und nach der Aktivierung. Die Einsparung hängt davon ab, wie viel Zustand Ihre Anwendung beim Import erstellt.

Preload hat eine Nebenwirkung, die beim Deployment oft überrascht. systemctl reload sendet HUP, und das dokumentierte Verhalten von Gunicorn bei HUP besteht darin, die Konfiguration neu zu laden und neue Worker zu starten. Wenn die Anwendung per Preload geladen wurde, importiert Gunicorn Ihren Code nicht erneut. Ihre neue Version läuft daher nicht, obwohl die Worker-Prozesse neu sind. Verwenden Sie systemctl restart nach einer Codeänderung. Wenn die alten Worker zuerst ihre laufenden Anfragen beenden sollen, verwenden Sie die Sequenz USR2 und anschließend WINCH.

Wie viele Worker kann ein 1-GB-VPS realistisch ausführen?

ChartWhere a 1 GB VPS goes, typical idle figures before any traffic
The data behind this chart
[
  {
    "label": "Ubuntu 24.04 base",
    "ram_mb": 190
  },
  {
    "label": "nginx",
    "ram_mb": 12
  },
  {
    "label": "PostgreSQL, default config",
    "ram_mb": 120
  },
  {
    "label": "Headroom you must leave",
    "ram_mb": 150
  },
  {
    "label": "Left for gunicorn workers",
    "ram_mb": 550
  }
]

Das sind Leerlaufwerte auf einem System, das keine Anfragen verarbeitet. Etwa 550 MB bleiben für Application-Worker übrig, und das gilt noch vor dem ersten Request.

Teilen Sie diesen Wert nun, und rechnen Sie pessimistisch. Ein Request benötigt während seiner Verarbeitung Arbeitsspeicher: zunächst lädt ein Queryset einige tausend Zeilen, danach wird ein Template gerendert. Der Spitzenbedarf pro Worker liegt häufig nahe dem Doppelten des Leerlaufwerts. Planen Sie daher mit dem doppelten Wert. Django mit dem Admin benötigt im Leerlauf 58 MB. Auf diesem System erhalten Sie damit vier Worker. Flask mit SQLAlchemy benötigt 38 MB. Damit sind sieben Worker möglich.

Der Vorschlag (2 x cores) + 1 von Gunicorn setzt voraus, dass die CPU knapp ist, der Arbeitsspeicher aber nicht. Auf einem kleinen VPS ist das umgekehrt. Ein gemeinsam genutzter vCPU-Anteil liefert bei hoher Auslastung des Hosts außerdem weniger als die Rechenleistung eines vollständigen Cores. Das sollten Sie verstehen, bevor Sie Ihren Code verantwortlich machen: CPU-Steal-Time durch einen ausgelasteten Nachbarn wird in top als Wert st angezeigt.

Wenn Ihre Views überwiegend auf eine Datenbank oder eine Upstream-API warten, sind Threads hier besser geeignet als Prozesse. --worker-class gthread --workers 2 --threads 4 ermöglicht acht parallele Requests zum Speicherpreis von zwei Workern, weil die Threads eine geladene Kopie des Interpreters und des Frameworks gemeinsam verwenden. Der Global Interpreter Lock hilft bei einer View nicht, die CPU-Zeit verbraucht.

Richten Sie für das System Swap ein. Ein 1-GB-VPS ohne Swap wandelt einen Speicherbedarfssprung in einen beendeten Prozess um. Eine Swap-Datei wandelt denselben Sprung in einen langsamen Request um.

sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
echo 'vm.swappiness = 10' | sudo tee /etc/sysctl.d/99-swappiness.conf
sudo sysctl --system

Begrenzen Sie anschließend die Anwendung selbst. MemoryMax=600M in der Gunicorn-Unit bewirkt, dass der Kernel den Arbeitsspeicher aus der cgroup Ihrer Anwendung zurückfordert, statt systemweit einen Prozess auszuwählen. Dadurch kostet Sie ein außer Kontrolle geratener Request nicht Ihre SSH-Sitzung.

Verhalten bei Kaltstarts und Neustarts

ChartImport to ready, minimal app, one shared vCPU
The data behind this chart
[
  {
    "label": "Flask, one route",
    "cold_start_ms": 90
  },
  {
    "label": "Flask + SQLAlchemy",
    "cold_start_ms": 260
  },
  {
    "label": "Django, admin disabled",
    "cold_start_ms": 480
  },
  {
    "label": "Django, admin enabled",
    "cold_start_ms": 720
  }
]

Die Startkosten fallen zweimal an: bei jeder Bereitstellung und bei jedem automatischen Neustart nach einem Absturz. Eine minimale Flask-Anwendung ist in etwa 90 ms bereit. Django benötigt mit aktiviertem Administrationsbereich auf derselben gemeinsam genutzten vCPU etwa 720 ms. Beide Werte sind typische veröffentlichte Richtwerte. Messen Sie Ihre Anwendung selbst, da Ihre Abhängigkeiten den größten Einfluss haben.

cd /srv/site1
DJANGO_SETTINGS_MODULE=site1.settings /srv/site1/.venv/bin/python -X importtime -c "import django; django.setup()" 2>&1 | tail -20

Die letzten Zeilen listen die langsamsten Imports mit ihrer kumulierten Dauer in Mikrosekunden auf. Für Flask führen Sie dasselbe Flag für Ihr Modul aus: python -X importtime -c "import app".

Mit aktiviertem Preload trägt der Master-Prozess diese Kosten einmal. Jeder per Fork gestartete Worker ist danach sofort bereit. Ohne Preload trägt jeder Worker die Kosten selbst. Das timeout von gunicorn umfasst dann sowohl den Start als auch die Verarbeitung der Anfrage. Ein Worker, der sich innerhalb von timeout Sekunden nicht meldet, wird beendet und ersetzt. Eine umfangreiche Anwendung auf einer langsamen gemeinsam genutzten vCPU kann dadurch in einer Neustartschleife hängen bleiben und keine einzige Anfrage bedienen. Das Log enthält folgende Meldung:

[2026-08-09 09:14:02 +0000] [981] [CRITICAL] WORKER TIMEOUT (pid:1004)

Migrationen gehören in die Unit und nicht in den Startcode der Anwendung. ExecStartPre wird einmal ausgeführt, bevor ein Worker existiert. Wenn Sie migrate in Ihrer Anwendung ausführen, konkurrieren drei Worker um dieselbe Schema-Sperre.

Die Bereitstellungsstruktur ist nahezu identisch

Prozessmanager

Beide Frameworks laufen unter gunicorn, und gunicorn läuft unter systemd.

[Unit]
Description=gunicorn for site1
After=network.target

[Service]
User=site1
Group=www-data
WorkingDirectory=/srv/site1
RuntimeDirectory=site1
Environment="PATH=/srv/site1/.venv/bin"
ExecStartPre=/srv/site1/.venv/bin/python manage.py migrate --noinput
ExecStart=/srv/site1/.venv/bin/gunicorn -c /srv/site1/gunicorn.conf.py site1.wsgi:application
Restart=on-failure
RestartSec=3
MemoryMax=600M

[Install]
WantedBy=multi-user.target

RuntimeDirectory=site1 erstellt /run/site1 beim Start und entfernt sie beim Stoppen. Der Socket-Pfad ist dadurch immer mit dem richtigen Besitzer vorhanden. Die Zeile umask = 0o007 in der gunicorn-Konfiguration macht diesen Socket für die Gruppe www-data schreibbar. So kann nginx darauf zugreifen.

Die Flask-Unit ist dieselbe Datei mit einer geänderten Zeile: ExecStart=... gunicorn -c /srv/site1/gunicorn.conf.py app:app, jedoch ohne ExecStartPre. Das Argument app:app besteht aus Modul und aufrufbarem Objekt. Der Fehler Failed to find attribute 'app' in 'app'. bedeutet daher, dass Ihr Modul keine Variable mit diesem Namen definiert. Geplante Aufgaben folgen demselben Muster. Ein systemd-Timer ersetzt cron für einen Django-Managementbefehl, ohne für einen Server dieser Größe eine Task Queue hinzuzufügen.

Statische Dateien

Django mit DEBUG = False liefert überhaupt keine statischen Dateien aus. Setzen Sie STATIC_ROOT, führen Sie python manage.py collectstatic aus und konfigurieren Sie den Webserver für das Ausgabeverzeichnis. Wenn Sie diesen Schritt überspringen, wird das Admin-Interface ohne Formatierung geladen, während das Log mit Not Found: /static/admin/css/base.css gefüllt wird.

Dafür gibt es zwei sinnvolle Möglichkeiten. Ein nginx-Block alias belastet Ihre Anwendung nicht. WhiteNoise wird als Middleware hinzugefügt und liefert die Dateien aus dem Worker aus. Dadurch entfällt der nginx-Block, allerdings fällt pro Datei etwas zusätzliche Worker-Zeit an. Flask liefert seinen eigenen Ordner static/ in der Entwicklung aus. In der Produktion konfigurieren Sie den Proxy aus demselben Grund für dieses Verzeichnis.

Reverse Proxy

server {
    listen 80;
    server_name example.com;

    location /static/ {
        alias /srv/site1/static/;
        expires 30d;
    }

    location / {
        proxy_pass http://unix:/run/site1/gunicorn.sock;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Hinter jedem Proxy muss Django wissen, dass die ursprüngliche Anfrage über HTTPS eingegangen ist. Andernfalls lehnen die CSRF-Prüfungen Ihre eigenen Formulare ab.

ALLOWED_HOSTS = ["example.com"]
CSRF_TRUSTED_ORIGINS = ["https://example.com"]
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

Wenn auf dem Server bereits Container laufen, erledigt Traefik vor mehreren Docker-Compose-Anwendungen dieselbe Aufgabe mit Labels am Container statt mit einer Datei pro Website.

Welche Datenbank

SQLite ist für einen einzelnen Anwendungsserver mit einer Schreibrate von wenigen Vorgängen pro Sekunde tatsächlich geeignet. Außerdem entfällt ein kompletter Daemon aus dem Speicherbudget. Aktivieren Sie Write-Ahead-Logging (WAL) und setzen Sie für den Treiber ein Timeout bei belegter Datenbank.

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
        "OPTIONS": {
            "timeout": 20,
            "init_command": "PRAGMA journal_mode=WAL;",
        },
    }
}

Ohne diese beiden Optionen stoßen Sie beim ersten gleichzeitigen Schreibzugriff von zwei Workern auf django.db.utils.OperationalError: database is locked. Der standardmäßige Journal-Modus blockiert während eines Schreibvorgangs die Leser, und das standardmäßige Timeout läuft fast sofort ab. Die ausführlichere Begründung, einschließlich des Punkts, an dem SQLite nicht mehr die richtige Wahl ist, finden Sie unter SQLite produktiv auf einem VPS betreiben.

PostgreSQL auf demselben Server mit 1 GB belegt die im Budget oben veranschlagten 120 MB. Zusätzlich wird für jede persistente Verbindung ein Backend-Prozess benötigt. Djangos CONN_MAX_AGE hält pro Worker eine Verbindung offen. Vier Worker bedeuten daher vier Backends. Das ist normalerweise ein guter Kompromiss. Berücksichtigen Sie diesen Verbrauch, bevor Sie die Worker-Anzahl festlegen. Wenn Sie die Datenbank lieber in einem Container neben der Anwendung betreiben möchten, ist Docker auf einem VPS betreiben derselbe Kompromiss mit einer höheren Mindestbelastung. Der Daemon und jeder Container verursachen zusätzlichen Overhead, der bei dieser Größe relevant ist.

Was die Batterien bringen und was sie kosten

Djangos zusätzliche Megabytes sind eine Sammlung von Komponenten, die bereits vorhanden sind und zusammenspielen: das ORM mit Migrationen, das Sitzungs- und Authentifizierungssystem, das Berechtigungsmodell, die Formularebene mit CSRF-Schutz, die Template-Engine, Management-Befehle und das Admin-Interface. Das Admin-Interface wird häufig unterschätzt. Es ist ein funktionsfähiger Datenbankeditor für Ihre Modelle, einschließlich Suche und Filtern, für eine Zeile in INSTALLED_APPS.

Flask ist das Gegenstück. Sie erhalten Routing, ein Request-Objekt, Jinja2-Templates und ein Konfigurationsobjekt. Alles Weitere ist eine Entscheidung, die Sie selbst treffen. Bei einer kleinen Anwendung ist das ein echter Vorteil, weil kein ORM geladen wird, wenn Sie keines importieren.

Die Falle liegt im Mittelweg. Fügen Sie SQLAlchemy für Modelle, Alembic für Migrationen, Flask-Login für Sitzungen, Flask-WTF für Formulare und CSRF sowie eine Admin-Erweiterung für einen Administrationsbereich hinzu, haben Sie etwas zusammengestellt, das Djangos Speicherprofil und keine seiner inneren Abstimmungen besitzt. Jede Komponente hat ihren eigenen Release-Zyklus und ihre eigene Vorstellung davon, wie die Anwendung integriert werden sollte. An diesem Punkt ist Django die günstigere Lösung – sowohl beim RAM-Verbrauch als auch bei der Zeit, die Sie für Upgrades aufwenden.

Django vs. Flask: die Entscheidungsregel

Verwenden Sie Django, wenn die Anwendung Benutzerkonten und bearbeitbare Inhalte hat, sich ihr Schema laufend ändern wird und ein Backoffice vorhanden ist, das tatsächlich genutzt wird. Verwenden Sie Flask, wenn die Anwendung eine JSON-Schnittstelle über einem bereits vorhandenen Datenspeicher oder einen Webhook-Receiver ohne HTML ist.

Der entscheidende Vergleich ist eine schriftliche Liste. Notieren Sie jedes Paket, das Sie in Flask installieren würden, um den benötigten Funktionsumfang zu erreichen. Wenn diese Liste ein ORM und ein Migrationstool enthält, haben Sie sich bereits für Django entschieden und zahlen zusätzlich dafür, dieses Ziel langsamer zu erreichen.

Ein Fall spricht bei schwacher Hardware tatsächlich für Flask: mehrere kleine Dienste auf einem Rechner. Jeder Flask-Dienst läuft als eigener, ressourcengünstiger Prozess unter einer eigenen Unit. Drei Django-Sites auf einem 1 GB VPS bedeuten, dass gleichzeitig drei Kopien des Frameworks im Speicher liegen. Dann geht die obige Rechnung nicht mehr auf. Wenn die Ressourcen weiterhin nicht ausreichen, ist ein größerer Tarif oft die ehrliche Lösung. Was ein VPS tatsächlich pro Monat kostet lässt sich kürzer besprechen, als eine funktionierende Anwendung neu zu schreiben.

Fehlerbilder mit den angezeigten Meldungen

Worker verschwinden und kommen wieder. Gunicorn gibt [ERROR] Worker (pid:1234) was sent SIGKILL! Perhaps out of memory? aus. Das geschieht sowohl, wenn Gunicorn einen Worker beendet, der seinen Timeout-Heartbeat verpasst hat, als auch, wenn der Kernel den Prozess beendet hat. Unterscheiden Sie die beiden Fälle mit dmesg -T | grep -i "killed process". Eine entsprechende Zeile weist auf Speichermangel hin. Verringern Sie die Anzahl der Worker oder fügen Sie Swap hinzu.

Jede Seite gibt 400 zurück und das Log enthält Invalid HTTP_HOST header. Die vollständige Meldung lautet Invalid HTTP_HOST header: 'example.com'. You may need to add 'example.com' to ALLOWED_HOSTS.. Django weist die Anfrage zurück, bevor sie Ihren Code erreicht, weil ALLOWED_HOSTS leer ist oder den Namen nicht enthält, den der Proxy in Host übergeben hat.

Formulare schlagen mit Origin checking failed fehl. Die Seite meldet, dass die CSRF-Prüfung fehlgeschlagen ist. Das geschieht hinter einem Proxy mit TLS-Terminierung: Die Anwendung sieht plain HTTP, erzeugt eine http://-Origin und vergleicht sie mit einer Anfrage, die über https:// eingegangen ist. Setzen Sie SECURE_PROXY_SSL_HEADER und CSRF_TRUSTED_ORIGINS, und prüfen Sie, ob der Proxy tatsächlich X-Forwarded-Proto sendet.

nginx gibt sofort 502 zurück. Das Error-Log nennt den Grund: connect() to unix:/run/site1/gunicorn.sock failed (2: No such file or directory) bedeutet, dass die Unit nicht läuft, und (13: Permission denied) bedeutet, dass der Socket vorhanden ist, nginx ihn aber nicht öffnen kann. Ursache sind die Einstellung für umask und die Gruppe.

Im Admin-Bereich fehlen die Formatierungen. collectstatic wurde nicht ausgeführt, oder der Pfad alias stimmt nicht mit STATIC_ROOT überein. Das Access-Log zeigt 404-Fehler unter /static/admin/.

Schreibvorgänge schlagen bei geringer Last fehl. database is locked aus SQLite bedeutet, dass WAL deaktiviert ist oder der Busy-Timeout für zwei Worker, die gleichzeitig schreiben, zu kurz eingestellt ist.

FAQ

Ist Django für einen VPS mit 1 GB zu umfangreich?

Nein. Django läuft mit wenigen Workern, nginx davor und SQLite dahinter problemlos auf 1 GB. Eng wird es, wenn Sie PostgreSQL mit den Standardeinstellungen, einen Cache, einen Hintergrund-Worker und Docker auf demselben System betreiben. Messen Sie die proportional set size eines Workers, verdoppeln Sie den Wert für Lastspitzen bei Anfragen und vergleichen Sie ihn mit dem Speicher, der nach dem Betriebssystem und der Datenbank noch verfügbar ist.

Wie viele gunicorn-Worker sollte ich auf einer vCPU ausführen?

Beginnen Sie mit drei Workern und messen Sie. Auf einem kleinen VPS ist der Arbeitsspeicher normalerweise der begrenzende Faktor. Teilen Sie den nach dem Betriebssystem und der Datenbank verbleibenden Arbeitsspeicher durch das Doppelte der proportional set size eines Workers. Wenn Ihre Views überwiegend auf eine Datenbank oder eine Upstream-API warten, wechseln Sie zur Worker-Klasse gthread. Verwenden Sie wenige Worker mit jeweils mehreren Threads. Threads nutzen eine gemeinsam geladene Kopie des Frameworks und benötigen deutlich weniger Arbeitsspeicher als zusätzliche Prozesse.

Benötige ich PostgreSQL, oder reicht SQLite aus?

SQLite reicht für einen einzelnen Anwendungsserver mit moderatem Schreibaufkommen aus und entfernt einen vollständigen Daemon aus dem Speicherbudget. Aktivieren Sie write-ahead logging und setzen Sie ein busy timeout. Andernfalls schlagen gleichzeitige Schreibvorgänge mit database is locked fehl. Wechseln Sie zu PostgreSQL, wenn mehr als eine Maschine schreiben muss oder wenn Sie Funktionen benötigen, die SQLite nicht bietet, beispielsweise gleichzeitige umfangreiche Schreibvorgänge oder eine rollenbasierte Zugriffskontrolle.

Sollte ich uvicorn anstelle von gunicorn ausführen?

Nur wenn Sie async Views und eine tatsächliche Warteoperation haben. Flask ist eine WSGI-Anwendung. Eine async View läuft daher in einer neuen Eventloop-Instanz innerhalb des Worker-Threads und wird vor dem Start der nächsten Anfrage beendet. Dadurch entsteht keine zusätzliche Nebenläufigkeit. Django async Views benötigen einen ASGI-Server, damit sie überhaupt unterstützt werden. Neuere uvicorn-Releases haben ihre gunicorn-Worker-Klasse in ein separates Paket verschoben. Lesen Sie daher die aktuelle uvicorn-Dokumentation, statt ein altes Worker-Klassen-Flag aus einem älteren Tutorial zu übernehmen.

Warum ist mein Worker ohne Traceback verschwunden?

Ein Prozess, den der Out-of-Memory-Killer des Kernels beendet, erhält SIGKILL und kann beim Beenden nichts mehr protokollieren. Ihr Anwendungslog endet daher einfach. Gunicorn erkennt die Unterbrechung und gibt Worker (pid:1234) was sent SIGKILL! Perhaps out of memory? aus. Bestätigen Sie dies mit dmesg -T | grep -i "killed process". Die Lösung besteht in weniger Workern oder einer Swap-Datei. Dadurch wird eine Speicherspitze zu einer langsamen Anfrage statt zu einem beendeten Prozess.

#django#flask#python#gunicorn#deployment#vps