RE:NODE

Kolik RAM potřebuje Python aplikace: workery a dimenzování

Text: Tým RE:NODE
19 min čtení


Malá Python webová aplikace potřebuje 100 až 300 MB na jeden worker a spustíš dva až čtyři workery, takže pro typické API stačí 1 GB a 2 GB je velikost, do které se obvykle pohodlně vejde Django nebo aplikace s těžkými daty. To je výchozí odhad, ne odpověď. Záleží jen na jednom čísle: na resident paměti jednoho workeru tvé aplikace, změřené po skutečném provozu, vynásobené počtem workerů a navýšené o rezervu na špičky. Tento článek ukáže, jak to číslo získat, jak z něj udělat počet workerů pro gunicorn nebo uvicorn a jak poznat, kdy je správným řešením větší tarif, a ne další ladění.

Jde jen o paměť. Jak aplikaci nasadit, navázat port a předat proxy hlavičky, najdeš v článcích nasazení FastAPI nebo Flasku a nasazení Djanga do produkce. O zamykání závislostí pojednává requirements a virtualenv v Pythonu. Obdoba tohoto článku pro Node je limity paměti v Node.

Proč Python zabírá víc paměti, než čekáš#

Python drží všechno jako objekty. Celé číslo, krátký řetězec, klíč slovníku - každé má hlavičku, počítadlo referencí a ukazatel na typ, takže seznam milionu drobných hodnot stojí desítky megabajtů, ne těch pár, které by naznačovala surová data. Samotný interpret s moduly standardní knihovny, které tvůj framework importuje, zabere zhruba 10 až 20 MB dřív, než tvůj kód udělá cokoli.

Navrch platíš za tři věci, které nesouvisejí s tvou logikou:

  • Importované knihovny. Holá Flask aplikace je malá. Django s ORM, administrací a několika aplikacemi je podstatně větší. Cokoli, co importuje NumPy, pandas, SQLAlchemy s mnoha modely, Pydantic s velkou sadou schémat nebo knihovnu pro strojové učení, přidá jen samotným importem 100 MB i víc.
  • Alokátor. CPython vydává drobné objekty z vlastních arén a operačnímu systému je vrací jen výjimečně. Po požadavku, který krátce vystavěl velkou strukturu, proces paměť drží a později ji znovu využije. Graf vyleze nahoru a zůstane - to je normální a samo o sobě to není únik paměti.
  • Pracovní paměť požadavku. Zpracování JSON těla o 5 MB, vykreslení šablony, sestavení odpovědi z databázového dotazu na 20 000 řádků - každé z toho je krátkodobá špička nad základní úrovní a právě špička rozhoduje, jestli se vejdeš.

Praktický důsledek: paměť Pythonového procesu roste jako po schodech. Stoupne při zahřátí, znovu stoupne, když se poprvé zavolá těžký endpoint, a pak se ustálí. Hodnota odečtená hned po startu serveru je příliš optimistická.

Hrubé rozsahy, abys měl s čím porovnávat. Jsou to typická pozorování, ne záruky; tvé závislosti je hodně mění.

Typ aplikaceRSS na worker, zahřátýPoznámka
Flask, pár route40-80 MBHlavně interpret a framework
FastAPI s modely Pydantic60-120 MBRoste s počtem modelů
Django s ORM a administrací90-180 MBVíc u mnoha nainstalovaných aplikací
Cokoli z toho plus pandas150-350 MBDominují NumPy a pandas
Cokoli, co načítá ML modelVelikost modelu plus 150 MBČasto sám zabere celý tarif

RSS, VSZ a co vlastně měřit#

Linux hlásí o procesu několik údajů o paměti a pro dimenzování je užitečný jen jeden.

  • VSZ (virtual size) počítá všechny adresy, které proces namapoval, včetně těch, které si rezervoval a nikdy se jich nedotkl. U drobného Python procesu to může být několik gigabajtů a nic to neříká.
  • RSS (resident set size) je fyzická paměť, kterou proces právě drží v RAM. Čti tento údaj, s jedním háčkem uvedeným níže.
  • PSS (proportional set size) dělí sdílené stránky mezi procesy, které je sdílejí. Je to férové číslo, když je několik workerů vytvořeno forkem z jednoho rodiče.

Háček: RSS počítá sdílené stránky v plné výši za každý proces. Čtyři workery gunicornu vytvořené forkem z jednoho masteru ukazují třeba po 120 MB RSS, ale velká část je tentýž kód a data jen pro čtení, ve fyzické paměti uložená jednou. Součet RSS přes workery celek nadhodnocuje. Součet PSS vychází správně.

Začni jednoduchým pohledem. V jakémkoli shellu Linuxu, včetně SSH relace na VDS:

bash
$ ps -eo pid,ppid,rss,vsz,cmd --sort=-rss | head -8

rss je v kilobajtech. Uvidíš master gunicornu a každý worker jako zvláštní řádek. Pro jeden proces je vlastní hlášení jádra tohle:

bash
$ grep -E 'VmRSS|VmHWM' /proc/$(pgrep -o -f gunicorn)/status

VmRSS je aktuální resident velikost a VmHWM je nejvyšší, jaká kdy byla - high-water mark, tedy číslo, které chceš, když rozhoduješ, kolik stojí nejhorší případ. Aby sis nechal spočítat PSS pro celou skupinu workerů, udělá aritmetiku smem:

bash
$ smem -P gunicorn -k -c "pid pss rss command"

Pokud chybí, nainstaluj ho z distribuce. Pokud tohle spustit nemůžeš - na spravovaném kontejneru máš jen konzoli - použij samotnou aplikaci. Je to malý endpoint nebo řádek v logu, který hlásí vlastní čísla:

python
import osimport resourcedef memory_report() -> dict:    # ru_maxrss is kilobytes on Linux (bytes on macOS)    peak_mb = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024    with open("/proc/self/status") as f:        rss_kb = next(int(l.split()[1]) for l in f if l.startswith("VmRSS"))    return {"pid": os.getpid(), "rss_mb": rss_kb // 1024, "peak_mb": round(peak_mb)}

Vystav ho na interní route nebo ho loguj každých pár minut. Protože je každý worker samostatný proces, pid ve výstupu říká, který worker odpověděl; zavolej ho několikrát a uvidíš různé pid s různými čísly, což je samo o sobě poučné.

Co tarif doopravdy omezuje#

Paměť tarifu je limit pro celý kontejner, ne pro jeden proces. Všechno uvnitř se počítá dohromady: master gunicornu, každý worker, každé vlákno na pozadí nebo podproces a page cache souborů, které tvá aplikace čte. V kontejneru s limity cgroup si limit i aktuální spotřebu přečteš přímo:

bash
$ cat /sys/fs/cgroup/memory.max$ cat /sys/fs/cgroup/memory.current$ cat /sys/fs/cgroup/memory.peak

To je cgroup v2, dnes běžné uspořádání; na starších hostitelích s cgroup v1 jsou soubory /sys/fs/cgroup/memory/memory.limit_in_bytes a memory.usage_in_bytes. memory.max je strop v bajtech, memory.current je to, co skupina využívá teď, a memory.peak (na novějších jádrech) je nejvyšší dosažená hodnota. memory.current zahrnuje souborovou cache, kterou jádro pod tlakem uvolní, takže může vypadat znepokojivě na serveru, který je v pořádku. Zastavení kvůli nedostatku paměti předpovídá součet anonymní paměti tvých procesů, který je blízký součtu PSS.

Na tom, co se děje na limitu, závisí, kolik rezervy nechat. Na RE:NODE, když server dosáhne limitu paměti, jádro kontejner zastaví a ten se čistě znovu spustí, místo aby se propadl do swapu; to je zdokumentované chování platformy. Žádná pomalá degradace, kterou bys mohl sledovat, neexistuje - proces prostě zmizí, rozpracované požadavky se ztratí a aplikace nastartuje znovu. Upstream Pterodactyl se chová opačně a nechá kontejner swapovat, což udrží vše naživu a udělá to hrozně pomalé. V obou případech je ponaučení stejné: neplánuj provoz na hraně. Mechanismus vysvětluje swap v Linuxu a OOM killer.

Konzole panelu ukazuje grafy paměti, CPU a disku proti limitům tarifu. Ten graf je nejrychlejší kontrola reality: nech ho otevřený při zátěžovém testu a sleduj, kde čára leží vůči stropu. Co všechno je na té obrazovce, popisuje čtení konzole.

Workery, vlákna a vzorec#

„Worker“ gunicornu nebo uvicornu je proces operačního systému s vlastní kopií paměti tvé aplikace. Workery existují ze dvou důvodů: aby využily víc než jedno jádro CPU (global interpreter lock Pythonu dovoluje jednomu procesu vykonávat bytecode Pythonu na jednom jádru) a aby se dál obsluhovalo, zatímco jiný worker čeká nebo se restartuje. Jediný požadavek nezrychlí.

Z toho plyne vzorec pro dimenzování a má dva limity, ne jeden:

code
workers_by_memory = floor((plan_RAM - baseline - headroom) / RSS_per_worker)workers_by_cpu    = vCPU, rounded up, times 1 to 2 for I/O-bound appsworkers           = min(workers_by_memory, workers_by_cpu)

baseline je to, co kontejner utratí za věci, které nejsou workery: master gunicornu (zhruba 30 až 60 MB), případný plánovač a rezerva na shell a logování. headroom je bezpečnostní rezerva; počítej 20 až 25 procent tarifu. RSS_per_worker je tvůj změřený high-water mark na worker, ne hodnota při startu.

Běžná rada (2 x cores) + 1 workerů je pravidlo pro CPU z doby synchronních, na I/O závislých aplikací na velkých strojích. Na malém tarifu, kde je těsnější paměť, běžně dává víc workerů, než se vejde. Údaj vCPU tarifu je navíc tvrdé omezení na zakoupený podíl: tarif s 0,5 nebo 1 vCPU nezíská z pěti workerů zatěžujících CPU nic než pětinásobný účet za paměť.

Projděme příklad. Služba ve FastAPI změřená na 110 MB na worker na high-water mark, na tarifu 1 GB:

code
plan RAM        1024 MBbaseline          60 MB   (master, logging)headroom         250 MB   (about 25 percent)left for workers 714 MBworkers by memory = floor(714 / 110) = 6workers by CPU    = 1 vCPU, async I/O-bound -> 2 to 3workers           = 2 or 3

Paměť by dovolila šest; CPU víc než tři nepotřebuje. Spusť tři a využiješ asi 390 MB tarifu, takže zbyde místo na špičky. Na malých tarifech je to obvyklý výsledek: limit CPU zabere dřív než limit paměti, dokud je aplikace lehká.

Teď stejná aritmetika pro Django s pandas při 280 MB na worker na tarifu 2 GB: (2048 - 60 - 500) / 280 je 5,3, tedy pět podle paměti, a s 1 vCPU bys spustil dva nebo tři. Pokud pak přidáš worker Celery nebo druhý proces, nejdřív odečti jeho paměť.

Vlákna jsou levnější páka. Worker gthread v gunicornu s několika vlákny sdílí jednu kopii paměti aplikace mezi všemi vlákny, takže vlákna násobí souběžnost bez násobení RSS. U synchronní aplikace ve Flasku nebo Djangu, která čeká na databázi, obslouží --workers 2 --threads 4 osm požadavků naráz zhruba za paměť dvou procesů. Daní je GIL: vlákna pomáhají při čekání na I/O, ne při výpočtech, a tvůj kód i knihovny musí být vláknově bezpečné.

Konfigurace gunicornu#

Nastavení dej do souboru, ne do dlouhého příkazového řádku, aby šlo projít v review. gunicorn.conf.py se čte automaticky z pracovního adresáře:

gunicorn.conf.py
import osbind = f"0.0.0.0:{os.environ.get('PORT', '8000')}"workers = int(os.environ.get("WEB_CONCURRENCY", "2"))threads = int(os.environ.get("GUNICORN_THREADS", "1"))worker_class = "gthread" if threads > 1 else "sync"timeout = 30graceful_timeout = 30keepalive = 5# Recycle workers to cap slow growth. Jitter staggers the restarts.max_requests = 1000max_requests_jitter = 100# Put the heartbeat file in RAM, not on disk.worker_tmp_dir = "/dev/shm"

Nastavení, na kterých pro paměť záleží:

NastaveníVýchozíVliv na paměť
workers1Každý je plná kopie aplikace
threads1Souběžnost bez dalších kopií
max_requests0 (vypnuto)Restartuje worker po N požadavcích
max_requests_jitter0Náhodně posune toto N, aby se workery nerestartovaly naráz
preload_appFalseNačte aplikaci jednou v masteru, pak forkne
timeout30Zabije worker, který tolik sekund mlčí

max_requests je bezpečnostní pás, ne oprava. Pokud worker roste o 1 MB na sto požadavků kvůli cache nebo úniku, recyklace po tisíci požadavcích škodu omezí, než najdeš příčinu. Zároveň ale skrývá skutečný únik v grafu, takže ho nastav vědomě a hledej dál. Jitter přidej vždy; bez něj všechny workery dosáhnou stejného počtu ve stejném okamžiku a aplikace se krátce zasekne, protože se všechny restartují najednou.

preload_app = True (nebo --preload) importuje aplikaci v masteru před forkem. Forknuté workery pak sdílejí už načtené stránky, což snižuje celkovou paměť a zrychluje starty. Je to skutečné, ale má to dvě omezení. Počítání referencí v Pythonu zapisuje do objektů při jejich používání, takže sdílené stránky po jedné „špiní“ a úspora se časem eroduje; čekej menší zisk, než naznačují schémata. A cokoli vzniklé při importu - databázová spojení, thread pooly, otevřené sockety - sdílí teď všechny forknuté workery, což věci rozbíjí. Spojení otevírej v hooku post_fork nebo líně při prvním použití. Connection pooly a limity vysvětlují, proč pool na každý worker násobí i počet spojení s databází.

Řádek worker_tmp_dir je malý, ale stojí za to: gunicorn píše pro každý worker heartbeat soubor a na některých souborových systémech kontejnerů to jde na pomalý disk a způsobuje falešné timeouty workerů. /dev/shm je v paměti, takže stojí pár kilobajtů RAM a problém odstraní.

Konfigurace uvicornu#

Uvicorn spouští ASGI aplikace (FastAPI, Starlette, Django Channels). Sám umí nastartovat i několik procesů:

bash
$ uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2 --limit-max-requests 5000

--workers N forkne N procesů. --limit-max-requests způsobí, že proces po tolika požadavcích skončí, aby ho supervizor nahradil; u prostého uvicorn --workers rodič znovu spustí mrtvý worker, takže to funguje i jako recyklace, i když méně elegantně než u gunicornu. Pokud máš jediný worker, --workers úplně vynech - běh v jednom procesu je jednodušší a v ps je to jeden řádek.

Pro produkci je běžný vzor, kdy gunicorn slouží jako správce procesů a práci dělá uvicorn:

bash
$ gunicorn main:app -k uvicorn.workers.UvicornWorker -w 2 -b 0.0.0.0:8000

Dostaneš recyklaci gunicornu (--max-requests), jeho řízené restarty a timeouty, s ASGI serverem uvnitř. Jednu věc si ověř u svých verzí: v nedávných vydáních je uvicorn.workers zastaralé ve prospěch samostatného balíčku uvicorn-worker a třída se pak jmenuje uvicorn_worker.UvicornWorker. Čti poznámky k verzi, kterou zamykáš, a nekopíruj příkaz ze starého návodu.

Async mění počty. Jeden async worker unese tisíce nečinných spojení a spoustu rozpracovaných požadavků, protože čekání stojí skoro nic. Async API se proto obvykle dimenzuje podle CPU, ne podle paměti: na malém tarifu je často správný jeden nebo dva workery a další přidávají jen paralelismus pro práci zatěžující CPU. Výjimkou je blokující event loop - volání synchronní knihovny nebo těžký výpočet uvnitř async def zastaví všechny požadavky v tomto workeru a řešením je víc workerů nebo přesun práce jinam, ne větší tarif.

Měření pod zátěží#

Číslo z nečinného serveru je k ničemu; měř, když aplikace pracuje. Opakovatelný postup:

  1. Spusť aplikaci s plánovaným počtem workerů a zapiš ps RSS každého. To je spodní mez.
  2. Přehraj realistický provoz. Stačí nástroj pro zátěž jako hey nebo wrk mířící na nejpomalejší a nejtěžší endpointy:

``bash $ hey -z 120s -c 20 https://your-app.example/api/heavy-endpoint ``

  1. Během běhu každých pár sekund zapisuj paměť do souboru:

``bash $ while true; do date +%T; ps -C gunicorn -o rss= | awk '{s+=$1} END {print s/1024 " MB"}'; sleep 5; done ``

  1. Zastav zátěž a zapisuj ještě minutu. Paměť, která se nevrátí dolů, je zadržený pool, což je do určité míry očekávané.
  2. Zaznamenej nejvyšší VmHWM kteréhokoli workeru a nejvyšší součet. To je RSS_per_worker pro vzorec výše.

Opakuj po nasazení, které mění závislosti, a po všem, co zasahuje velkou cestu kódem. Nová knihovna může přidat 80 MB na worker, aniž by se změnil jediný řádek tvého kódu.

Když je jeden worker mnohem větší než ostatní nebo graf roste bez ustálení, máš únik nebo neomezenou cache. Standardní tracemalloc z Pythonu ukáže, odkud alokace pocházejí:

python
import tracemalloctracemalloc.start(10)def top_allocations(limit: int = 10):    snapshot = tracemalloc.take_snapshot()    for stat in snapshot.statistics("lineno")[:limit]:        print(stat)

Pořiď dva snapshoty s odstupem několika minut a porovnej je pomocí snapshot2.compare_to(snapshot1, "lineno"), abys viděl, co vyrostlo. Samotný tracemalloc stojí paměť i rychlost, takže ho používej na stagingové kopii nebo krátce. Pro alokace nativních rozšíření, které tracemalloc nevidí, je těžší nástroj memray. Obvyklí viníci jsou cache v procesu bez limitu velikosti (použij functools.lru_cache(maxsize=...) nebo externí cache), globální seznam, do kterého se při každém požadavku přidává, těla odpovědí držená ve struktuře „posledních požadavků“ a výsledky ORM, které drží při životě nějaký odkaz. Obecnou metodu, jak rozlišit únik od ustálení, popisují úniky paměti herních serverů.

Špičky, nahrávání a práce, která se do požadavku nevejde#

Zabíjí tě špička, ne průměr v grafu paměti. Většinu špiček v Python webových aplikacích způsobují tři vzorce:

  • Čtení celých souborů do paměti. await request.body() nebo request.files["upload"].read() na nahrávce o 200 MB drží všech 200 MB v tom workeru a dvě souběžné nahrávky drží 400. Zapisuj na disk po kouscích a nastav limit velikosti těla na proxy nebo ve frameworku.
  • Neomezené dotazy. Model.objects.all() nebo SELECT * do seznamu načte každý řádek jako objekty Pythonu, což stojí mnohonásobek velikosti dat v databázi. Stránkuj, používej serverové kurzory nebo iterátory (.iterator() v Djangu) a vybírej jen potřebné sloupce.
  • Endpointy pro reporty a exporty. Sestavení CSV nebo PDF přímo ve webovém požadavku je klasický způsob, jak jeden uživatel shodí aplikaci. Generuj je v úloze na pozadí a vrať odkaz.

To poslední je strukturální oprava pro těžkou práci: přesuň ji pryč z webových workerů. Samostatný proces pro úlohy drží webovou vrstvu malou a předvídatelnou a z paměti úlohy dělá samostatné rozhodnutí. Možnosti popisují úlohy na pozadí na malém serveru a fronty úloh s Valkey. Pamatuj, že spouštěč úloh ve stejném kontejneru se přičítá ke stejnému limitu paměti, takže ho zahrň do baseline.

Znamení, že jsi tarif přerostl#

Ne každý problém s pamětí se řeší penězi a ne každý laděním. Tohle ukazuje, kam který patří.

Nejdřív laď (větší tarif nepomůže):

  • Paměť stoupá a nikdy se neustálí, jak dlouho by ses ptal čekal. To je únik; víc RAM jen odloží restart.
  • Každou špičku způsobuje jeden endpoint. Oprav endpoint.
  • Počet workerů je vyšší než dvojnásobek vCPU při malém provozu. Platíš za nečinné kopie.
  • Jediný požadavek načítá neomezenou sadu výsledků.

Přejdi na vyšší tarif:

  • I s rozumným počtem workerů a bez úniků leží zahřátý součet na high-water nad zhruba 75 procenty tarifu. Na běžnou špičku nezbývá rezerva.
  • Kontejner se na limitu paměti zastavuje a restartuje a příčinou je legitimní práce - větší závislost, model, větší pracovní množina - ne závada.
  • Jsi na jednom workeru, protože víc se nevejde, a požadavky stojí ve frontě. Odezva roste s provozem, zatímco CPU není nasycené.
  • Graf CPU je přibitý na limitu tarifu a workery jsou už na něj nastavené. CPU je tvrdé omezení na zakoupený podíl, takže přibité CPU znamená pomalé odpovědi, ne chyby, a lékem je víc vCPU. V řadě Python tarify jdou takto: 1 GB s 0,5 vCPU, 2 GB s 1 vCPU, 4 GB s 1,5 vCPU, 6 GB s 2 vCPU a 8 GB s 3 vCPU, takže větší tarif přidá obojí.

Přechod na vyšší tarif na RE:NODE mění limity na serveru, který už máš; server se nepřestavuje, takže soubory a prostředí zůstanou, kde jsou. Žebříček z tarifů Pythonu: 1 GB od $4 měsíčně, 2 GB od $7, 4 GB od $12, 6 GB od $17, 8 GB od $23. Před rozhodnutím zkontroluj aktuální ceny na stránce, protože se mohou měnit.

Takto předchozí příklady zapadají do tarifů:

ZátěžZměřenoTarif
Flask nebo FastAPI, 2-3 worker250-400 MB celkem1 GB
Django s administrací, 3 worker500-700 MB celkem2 GB
Django plus pandas, 3 worker1.2 GB celkem2 GB je těsné, 4 GB pohodlné
Aplikace držící ML modelvelikost modelu + 400 MBPočítej od velikosti modelu

Databáze a cache jsou zvlášť. Python aplikace, která ve stejném tarifu spouští i PostgreSQL a Valkey, s nimi soutěží o paměť, takže to nedělej; provozuj je jako samostatné servery. Stránka hosting bundles popisuje společný nákup aplikace, databáze a cache a bundle Python, PostgreSQL a Valkey existuje přesně pro tento stack.

Co se pokazí#

Workery umírají s `[CRITICAL] WORKER TIMEOUT` a v logu pak následuje restart. Buď požadavek skutečně trval déle než timeout, nebo byl worker zastaven zvenčí. Pokud čas odpovídá špičce paměti, kontejner byl nejspíš zastaven na limitu a hláška o timeoutu je jen příznak. Než zvýšíš timeout, zkontroluj memory.peak nebo graf v konzoli.

Aplikace se restartuje každých pár hodin bez chyby v logu. Zastavení kvůli nedostatku paměti aplikaci nedá šanci cokoli zapsat. Hledej tvar v grafu: stoupání, potom propad na dno. Oprav růst nebo zvyš limit; větší timeout nezmění nic.

Celková paměť je mnohem vyšší než `workers x RSS`. V kontejneru je ještě něco: podproces, který spouští tvůj kód, thread pool držící buffery nebo importovaná knihovna, která startuje proces na pozadí. Úplný seznam ukáže ps -eo pid,ppid,rss,cmd --sort=-rss.

Lokálně je paměť v pořádku a na serveru obří. Jiné verze závislostí, jiný build Pythonu nebo zapomenuté ladicí nastavení. Django s DEBUG = True drží v paměti každý SQL dotaz po celou dobu života procesu, což je u dlouho běžícího serveru únik už z návrhu. Nastav False.

Preload to zhoršil. Spojení vytvořená při importu a sdílená mezi forknutými workery dávají pod zátěží podivné chyby. Vytvářej je až po forku.

Všechno je v pořádku až do prvního požadavku dne. Líné importy a studená cache dělají první požadavky pomalé a jejich špičku paměti větší než v ustáleném stavu. Pokud na tom záleží, zavolej po startu z health checku zahřívací route.

FAQ#

Kolik workerů gunicornu mám spustit?

Vezmi menší ze dvou čísel: kolik workerů dovolí paměť (RAM tarifu minus baseline a 20 až 25 procent rezervy, děleno změřeným RSS na worker) a zhruba jeden až dva na vCPU u aplikace závislé na I/O. Na tarifu 1 GB s 1 vCPU jsou to obvykle dva nebo tři. Pokud aplikace čeká na databázi víc, než počítá, použij dřív vlákna než další procesy.

Vrací se paměť Python procesu někdy systému?

Částečně. CPython vrací operačnímu systému velké alokace, ale arény drobných objektů často drží a fragmentace může stránky přišpendlit. Proces, který zpracoval těžký požadavek, obvykle zůstane větší než předtím. Plánuj podle high-water marku, ne podle průměru, a použij max_requests, chceš-li pravidelné resety.

Mám použít async, abych ušetřil paměť?

Async šetří paměť, když máš mnoho souběžných spojení, která většinou čekají, protože jeden worker je unese všechna místo vlákna nebo procesu na každé spojení. Základní úroveň samotné aplikace nezmenší a práci zatěžující CPU nepomůže. Blokující volání uvnitř async funkce přínos ruší.

Vyplatí se zapnout --preload?

Někdy. Sdílí mezi workery kód a data načtená při importu a zkracuje start, ale úspora se zmenšuje, jak počítání referencí sahá na sdílené stránky, a každé spojení otevřené při importu se chybně sdílí. Než přínosu uvěříš, změř PSS s ním i bez něj.

Kolik paměti používá master gunicornu?

Typicky 30 až 60 MB sám o sobě a požadavky neobsluhuje. S preload_app drží i načtenou aplikaci. Při počítání ho zahrň do baseline.

Kdy je tarif 1 GB pro Python malý?

Když zahřátý součet potřebných workerů přesáhne zhruba tři čtvrtiny tarifu - například tři workery Djanga po 200 MB už zabírají 600 MB ještě před masterem a spouštěčem úloh. Pak přejdi na 2 GB, místo abys klesl na jeden worker a nechal požadavky stát ve frontě.

Pomohlo vám to?