लहान VPS वर Django की Flask: RAM आणि workers
1 ते 2 GB VPS वर Django आणि Flask साठी प्रति gunicorn worker लागणारी resident memory, तसेच छोटा server प्रत्यक्षात किती workers चालवू शकतो, याची मोजणी पहा.
लहान VPS वर Django आणि Flask ची किंमत
लहान VPS वर Django आणि Flask यांची तुलना करताना सर्वप्रथम memory चा विचार करावा लागतो. Django तुम्ही सुरू केलेल्या प्रत्येक worker process मध्ये त्याचा object relational mapper (ORM), migration machinery आणि ते enable केले असल्यास admin site load करते. Flask एक router आणि request object load करते. 1 GB च्या server वर या फरकामुळे एकाच वेळी किती workers चालू शकतात हे ठरते. Worker ची संख्या एकाच वेळी किती requests हाताळता येतील हे ठरवते.
Django ने दिलेल्या सुविधा तुम्ही पुन्हा स्वतः तयार करत नसाल, तरच त्यांची किंमत Django च्या वापराशी संबंधित ठरते. User accounts, sessions आणि admin panel असलेल्या अॅपसाठी Django योग्य आहे. प्रत्येक worker साठी लागणारी RAM ही तुम्हाला स्वतः लिहावी न लागलेल्या code ची किंमत आहे. तुम्ही आधीपासून चालवत असलेल्या datastore समोर JSON API हवी असल्यास Flask योग्य आहे, कारण त्यातील कोणतीही batteries कधीही load होणार नाहीत. हा योग्य पर्याय निवडण्याचा प्रश्न आहे. खालील मोजमापांवरून तुमचे अॅप या दोनपैकी कोणत्या प्रकारात येते ते ठरवता येईल.
एक gunicorn worker किती memory वापरतो?
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
}
]Ubuntu 24.04 वर Python 3.12, तीन gunicorn workers आणि preload सुरू असताना, प्रत्येक प्रकारच्या hello world अॅपसाठी प्रकाशित केलेले हे सर्वसाधारण आकडे आहेत. हे किमान मूल्य समजा, कारण तुमचे स्वतःचे imports यावर अतिरिक्त memory वापरतात. admin सुरू असलेला Django worker 96 MB resident memory वापरतो, तर memory मधील त्याचा proportional share 58 MB असतो. या दोन आकड्यांमधील फरक हा पुढील section चा संपूर्ण विषय आहे.
तुमच्या स्वतःच्या मशीनवर हेच मोजमाप तयार करा.
sudo apt update && sudo apt install -y python3-venv
python3 -m venv /srv/site1/.venv
/srv/site1/.venv/bin/pip install django gunicorn setproctitlesetproctitle install करा. ते उपलब्ध असल्यावर gunicorn आपल्या processes ची नावे gunicorn: master [site1] आणि gunicorn: worker [site1] अशी बदलतो. त्यामुळे पुढील commands workers चा अंदाज न लावता त्यांच्या नावाने शोधू शकतात.
pgrep -af gunicorn
ps -o pid,rss,args -p $(pgrep -d, -f 'gunicorn: worker')rss column ही kilobytes मधील resident set size आहे: process सध्या RAM मध्ये धरून ठेवलेले memory चे प्रत्येक page. Workers मधील त्यांची बेरीज केल्यास संख्या जास्त येते, कारण fork केलेला worker त्याच्या parent आणि इतर sibling workers सोबत pages share करतो. त्यामुळे तोच page अनेक वेळा मोजला जातो. त्याऐवजी kernel कडून proportional set size (PSS) मागवा. Processes ज्या shared page ला map करतात, त्यांच्यामध्ये PSS प्रत्येक page चे प्रमाणानुसार विभाजन करते.
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; doneWorkers ज्या user च्या मालकीचे आहेत त्या user म्हणून command चालवा किंवा sudo वापरा. Budget ठरवताना PSS column वापरा, कारण PSS ची बेरीज योग्य येते, RSS ची येत नाही.
Django मोठे आहे, कारण django.setup() जे काम करते ते अधिक आहे. ते INSTALLED_APPS मधील प्रत्येक entry import करते, application registry तयार करते आणि प्रत्येक model class सोबत त्यातील प्रत्येक field साठी एक Python object तयार करते. django.contrib.admin जोडल्यावर admin autodiscovery चालते. ते प्रत्येक app चे admin module import करते आणि त्यासोबत forms आणि template layers देखील load करते. Flask worker Werkzeug आणि Jinja2 import करतो आणि तिथे थांबतो.
एक महत्त्वाची मर्यादा लक्षात ठेवा: framework हा अनेकदा memory वापराचा छोटा भाग असतो. Cloud SDK किंवा numeric library import करणारा worker Django पेक्षा त्या libraries साठी अधिक memory वापरू शकतो. Framework ला समस्या मानण्यापूर्वी तुमचे वास्तविक app मोजा.
Copy on write आणि preload मुळे संख्या का बदलते
Gunicorn ची master process workers ला fork करते. fork() नंतर लगेच child parent सोबत प्रत्येक memory page share करतो आणि दोन्हीपैकी कोणतीही process त्या page वर लिहिते तेव्हाच kernel त्याची प्रत तयार करते. त्यामुळे सर्व्हरवर Django चे model registry एकदाच आहे की चार प्रती आहेत, हे fork होण्यापूर्वी ते कोणत्या process ने तयार केले यावर अवलंबून असते.
preload_app बंद असल्यास, प्रत्येक worker fork झाल्यानंतर तुमचे application import करतो. त्यामुळे प्रत्येक worker त्याची स्वतंत्र private copy तयार करतो. preload_app सुरू असल्यास, master application एकदाच import करतो आणि workers त्या pages चा वारसा घेतात.
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 ची कार्यपद्धती copy on write शी विसंगत आहे. प्रत्येक object header मध्ये reference count असतो. Object ला स्पर्श केल्यावर त्या header वर लेखन होते. त्यामुळे garbage collector heap तपासत असताना shared pages एकेक करून copy होतात. gc.freeze() आतापर्यंत allocate केलेल्या सर्व वस्तूंना permanent generation मध्ये हलवते. त्या generation ला collector पुन्हा भेट देत नाही. त्यामुळे अधिक pages shared राहतात. when_ready योग्य hook आहे, कारण तो preload नंतर आणि पहिला worker fork होण्यापूर्वी चालतो. तो जोडण्यापूर्वी आणि जोडल्यानंतर PSS मोजा. बचत तुमच्या application मधील import time state किती आहे यावर अवलंबून असते.
Deploy करताना preload चा एक खर्च अनेकांना अनपेक्षित वाटतो. systemctl reload, HUP पाठवते आणि HUP वर gunicorn चे documented behaviour configuration पुन्हा load करून नवीन workers सुरू करणे असे आहे. Application preloaded असल्यास तुमचा code पुन्हा import होत नाही. त्यामुळे worker processes नवीन असल्या तरी नवीन release चालू होत नाही. Code बदलल्यानंतर systemctl restart वापरा. जुन्या workers ना आधी काम पूर्ण करून बंद होण्याची संधी द्यायची असल्यास USR2 आणि त्यानंतर WINCH हा क्रम वापरा.
1 GB VPS प्रत्यक्षात किती workers चालवू शकतो?
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
}
]हे आकडे कोणतीही सेवा देत नसलेल्या सर्व्हरवरील idle स्थितीचे आहेत. Application workers साठी सुमारे 550 MB उरतात. पहिली request येण्यापूर्वीची ही उपलब्ध memory आहे.
आता भागाकार करा आणि pessimistic अंदाज वापरा. Request चालू असताना memory वापरते. उदाहरणार्थ, queryset काही हजार rows लोड करतो आणि त्यानंतर template render होते. प्रत्येक worker चा peak memory वापर idle आकड्याच्या जवळपास दुप्पट असतो. त्यामुळे budget करताना दुप्पट memory गृहीत धरा. Admin सह Django मध्ये idle memory 58 MB असल्यास या सर्व्हरवर चार workers चालतील. SQLAlchemy सह Flask मध्ये 38 MB असल्यास सात workers चालतील.
Gunicorn ची (2 x cores) + 1 सूचना CPU ही मर्यादित resource आहे आणि RAM पुरेशी आहे असे गृहीत धरते. लहान VPS वर परिस्थिती उलट असते. Host व्यस्त असताना एक shared vCPU देखील पूर्ण एका core एवढे काम देत नाही. त्यामुळे तुमच्या code ला दोष देण्यापूर्वी हे समजून घ्या: noisy neighbour मुळे होणारा CPU steal time top मध्ये st आकडा म्हणून दिसतो.
तुमच्या views प्रामुख्याने database किंवा upstream API ची वाट पाहत असतील, तर येथे processes पेक्षा threads अधिक योग्य ठरतात. --worker-class gthread --workers 2 --threads 4 दोन workers एवढ्या memory वापरून आठ concurrent requests देते, कारण threads interpreter आणि framework ची एकच loaded copy share करतात. Global interpreter lock मुळे CPU-intensive view साठी threads उपयुक्त ठरत नाहीत.
सर्व्हरवर swap द्या. Swap नसलेला 1 GB VPS memory spike झाल्यावर process बंद करतो. Swapfile असल्यास त्याच spike मुळे request मंद होते.
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त्यानंतर application वर स्वतःची मर्यादा लावा. Gunicorn unit मधील MemoryMax=600M मुळे kernel संपूर्ण सर्व्हरमधून एखादा process निवडण्याऐवजी तुमच्या app च्या cgroup मधून memory परत घेतो. त्यामुळे नियंत्रणाबाहेर गेलेल्या request मुळे तुमचे SSH session बंद होत नाही.
Cold start आणि restart चे वर्तन
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
}
]Boot ची किंमत दोनदा मोजावी लागते: प्रत्येक deploy वेळी आणि crash नंतर होणाऱ्या प्रत्येक automatic restart वेळी. एक minimal Flask अॅप सुमारे 90 ms मध्ये तयार होते, तर admin सक्षम असलेल्या Django ला त्याच shared vCPU वर सुमारे 720 ms लागतात. हे दोन्ही सामान्यतः प्रकाशित केलेले आकडे आहेत. तुमच्या dependencies चा परिणाम सर्वाधिक असल्यामुळे स्वतःचे मोजमाप करा.
cd /srv/site1
DJANGO_SETTINGS_MODULE=site1.settings /srv/site1/.venv/bin/python -X importtime -c "import django; django.setup()" 2>&1 | tail -20शेवटच्या ओळी cumulative microseconds नुसार सर्वाधिक वेळ घेणारे imports दाखवतात. Flask साठी तुमच्या module वर तोच flag चालवा: python -X importtime -c "import app".
preload सुरू असल्यास master ही किंमत एकदाच मोजतो आणि प्रत्येक fork केलेला worker त्वरित सुरू होतो. preload बंद असल्यास प्रत्येक worker ही किंमत मोजतो आणि gunicorn चे timeout boot तसेच request या दोन्हींवर लागू होते. timeout सेकंदांत check in न केलेल्या worker ला बंद करून त्याच्या जागी नवीन worker सुरू केला जातो. त्यामुळे slow shared vCPU वरील मोठे अॅप restart loop मध्ये अडकू शकते आणि कोणतीही विनंती पूर्ण करू शकत नाही. Log मध्ये असे दिसते:
[2026-08-09 09:14:02 +0000] [981] [CRITICAL] WORKER TIMEOUT (pid:1004)Migrations unit मध्ये असाव्यात; application startup code मध्ये नाहीत. ExecStartPre कोणताही worker सुरू होण्यापूर्वी एकदा चालते. migrate तुमच्या अॅपमध्ये ठेवल्यास तीन workers एकाच schema lock साठी परस्परांशी स्पर्धा करतात.
रचना जवळपास समान आहे
Process manager
दोन्ही frameworks gunicorn अंतर्गत चालतात आणि gunicorn 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.targetRuntimeDirectory=site1 सुरू होताना /run/site1 तयार करते आणि थांबताना ते काढून टाकते. त्यामुळे socket path योग्य owner सह नेहमी उपलब्ध असतो. gunicorn config मधील umask = 0o007 ही line त्या socket ला www-data group साठी writable करते. त्यामुळे nginx त्यापर्यंत पोहोचू शकतो.
Flask unit ही त्याच file ची आवृत्ती आहे. त्यात एक line बदलते: ExecStart=... gunicorn -c /srv/site1/gunicorn.conf.py app:app. ExecStartPre आवश्यक नाही. app:app argument मध्ये प्रथम module आणि नंतर callable येतो. त्यामुळे Failed to find attribute 'app' in 'app'. error चा अर्थ असा की तुमच्या module मध्ये त्या नावाचा variable define केलेला नाही. Scheduled work साठीही हीच पद्धत वापरता येते. या आकाराच्या box मध्ये task queue न जोडता Django management command साठी systemd timer cron ची जागा घेतो.
Static files
DEBUG = False असलेले Django static files अजिबात serve करत नाही. STATIC_ROOT सेट करा, python manage.py collectstatic चालवा आणि web server ला output directory कडे point करा. ही step वगळल्यास admin कोणत्याही styling शिवाय load होतो आणि log मध्ये Not Found: /static/admin/css/base.css भरत राहते.
Static files serve करण्याचे दोन योग्य मार्ग आहेत. nginx alias block मुळे तुमच्या app वर कोणताही भार पडत नाही. Middleware म्हणून जोडलेले WhiteNoise worker कडून files serve करते. त्यामुळे nginx block ची गरज राहत नाही; मात्र प्रत्येक file साठी worker चा थोडा वेळ खर्च होतो. Development मध्ये Flask स्वतःचे static/ folder serve करते. Production मध्ये त्याच कारणासाठी proxy ला त्या folder कडे point करा.
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;
}
}कोणत्याही proxy मागे Django वापरताना मूळ request HTTPS वरून आली होती हे Django ला सांगणे आवश्यक आहे. अन्यथा त्याच्या cross site request forgery (CSRF) checks तुमचे स्वतःचे forms reject करतात.
ALLOWED_HOSTS = ["example.com"]
CSRF_TRUSTED_ORIGINS = ["https://example.com"]
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")या box वर आधीच containers चालत असतील, तर अनेक Docker Compose apps समोर Traefik प्रत्येक site साठी स्वतंत्र file ऐवजी container वरील labels वापरून तेच काम करते.
Which database
प्रति सेकंद काही writes इतका write rate असलेल्या single application server साठी SQLite खरोखर योग्य आहे. त्यामुळे memory budget मधून एक पूर्ण daemon वगळता येतो. write ahead logging (WAL) सुरू करा आणि driver साठी busy timeout द्या.
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
"OPTIONS": {
"timeout": 20,
"init_command": "PRAGMA journal_mode=WAL;",
},
}
}हे दोन options नसतील, तर दोन workers एकाच वेळी write करण्याचा पहिलाच प्रयत्न करताना django.db.utils.OperationalError: database is locked आढळते. Default journal mode write सुरू असताना readers ला block करते आणि default timeout जवळजवळ लगेच प्रयत्न सोडतो. SQLite योग्य पर्याय राहात नाही ते कोणत्या टप्प्यावर, यासह सविस्तर माहिती VPS वर production मध्ये SQLite चालवणे येथे आहे.
त्याच 1 GB box वर PostgreSQL वापरल्यास वरील budget मध्ये 120 MB खर्च होतात. त्याशिवाय प्रत्येक persistent connection साठी एक backend process लागतो. Django चे CONN_MAX_AGE प्रत्येक worker साठी एक connection open ठेवते. त्यामुळे चार workers म्हणजे चार backends. हा खर्च सहसा योग्य trade-off असतो. मात्र worker number सेट करण्यापूर्वी त्याची गणना करा. Database app च्या शेजारी container मध्ये ठेवायचा असल्यास VPS वर Docker चालवणे हा त्याच trade-off चा पर्याय आहे. मात्र त्याची किमान overhead जास्त असते, कारण daemon आणि प्रत्येक container या आकाराच्या system वर महत्त्वाचा overhead वाढवतात.
बॅटऱ्यांमुळे काय मिळते आणि त्याची किंमत काय असते
Django चे अतिरिक्त मेगाबाइट्स म्हणजे आधीपासून उपलब्ध असलेल्या आणि एकमेकांसोबत कार्य करणाऱ्या सुविधांची यादी आहे: migrations सह ORM, session आणि authentication system, permission model, CSRF protection सह form layer, template engine, management commands आणि admin. लोक admin चे महत्त्व कमी लेखतात. तुमच्या models साठी हा search आणि filters असलेला कार्यरत database editor आहे; INSTALLED_APPS मध्ये फक्त एका ओळीत तो उपलब्ध होतो.
Flask याच्या अगदी उलट आहे. तुम्हाला routing, request object, Jinja2 templates आणि config object मिळतात. उरलेले सर्व तुम्ही निवडता. अॅप लहान असेल तेव्हा हे खरे मूल्य ठरते, कारण तुम्ही ORM import केला नाही तर तो load होत नाही.
मधला पर्याय धोकादायक ठरतो. Models साठी SQLAlchemy, migrations साठी Alembic, sessions साठी Flask-Login, forms आणि CSRF साठी Flask-WTF आणि back office साठी admin extension जोडल्यावर Django च्या memory profile एवढेच संसाधन वापरणारी, पण त्याच्यासारखी सुसंगतता नसलेली रचना तयार होते. प्रत्येक घटकाचे release cycle स्वतंत्र असते आणि अॅपची रचना कशी करावी याबद्दल त्याचे स्वतंत्र मत असते. याच टप्प्यावर Django अधिक स्वस्त पर्याय ठरतो—RAM च्या दृष्टीने आणि upgrades साठी तुम्ही खर्च करत असलेल्या वेळेच्या दृष्टीने.
Django विरुद्ध Flask: निर्णयाचा नियम
अॅपमध्ये user accounts, संपादित करता येणारी content, सतत बदलणारा schema आणि प्रत्यक्षात कोणी उघडेल असे back office असल्यास Django वापरा. आधीपासून अस्तित्वात असलेल्या datastore वर JSON interface म्हणून काम करणारे अॅप किंवा HTML नसलेला webhook receiver असल्यास Flask वापरा.
निर्णयासाठी लिखित यादी तयार करा. Flask मध्ये आवश्यक feature set मिळवण्यासाठी तुम्ही install करणार असलेले प्रत्येक package लिहून काढा. त्या यादीत ORM आणि migration tool असल्यास, तुम्ही Django आधीच निवडले आहे; फक्त तिथे पोहोचण्यासाठी अधिक खर्च आणि अधिक वेळ देत आहात.
लहान hardware वर एक बाब Flask च्या बाजूने जाते: एका box वर अनेक लहान सेवा चालवणे. प्रत्येक Flask सेवा तिच्या स्वतंत्र unit अंतर्गत स्वतंत्र, कमी संसाधने वापरणारी process असते. एका 1 GB VPS वर तीन Django sites म्हणजे framework च्या तीन copies एकाच वेळी memory मध्ये राहतात आणि वरील गणित लागू राहत नाही. गणना वारंवार अपुरी पडत असल्यास, मोठा plan घेणे हाच अनेकदा प्रामाणिक उपाय असतो. कार्यरत अॅप पुन्हा लिहिण्यापेक्षा VPS ची प्रत्यक्ष मासिक किंमत काय असते यावर चर्चा करणे सोपे असते.
तुम्हाला दिसणाऱ्या संदेशांसह अपयशाच्या स्थिती
Workers नाहीसे होतात आणि पुन्हा दिसतात. Gunicorn [ERROR] Worker (pid:1234) was sent SIGKILL! Perhaps out of memory? दाखवते. Timeout heartbeat चुकलेल्या worker ला ते बंद करते तेव्हाही आणि kernel ने process बंद केला तेव्हाही हा संदेश दिसतो. दोन्ही स्थितींमध्ये फरक करण्यासाठी dmesg -T | grep -i "killed process" वापरा. तिथे एखादी ओळ दिसत असल्यास कारण memory आहे; worker ची संख्या कमी करा किंवा swap जोडा.
प्रत्येक page 400 परत करते आणि log मध्ये Invalid HTTP_HOST header दिसते. पूर्ण संदेश Invalid HTTP_HOST header: 'example.com'. You may need to add 'example.com' to ALLOWED_HOSTS. असा आहे. तुमच्या code पर्यंत request पोहोचण्यापूर्वीच Django ती नाकारते, कारण ALLOWED_HOSTS रिकामे आहे किंवा proxy ने Host मध्ये पाठवलेले नाव त्यात समाविष्ट नाही.
Forms Origin checking failed मुळे अपयशी होतात. Page वर CSRF verification failed असे दिसते. TLS terminating proxy मागे हे घडते: app ला plain HTTP दिसते, त्यामुळे ती http:// origin तयार करते आणि https:// वरून आलेल्या request शी त्याची तुलना करते. SECURE_PROXY_SSL_HEADER आणि CSRF_TRUSTED_ORIGINS सेट करा आणि proxy खरोखर X-Forwarded-Proto पाठवतो याची खात्री करा.
nginx त्वरित 502 परत करते. Error log कारण दाखवते: connect() to unix:/run/site1/gunicorn.sock failed (2: No such file or directory) म्हणजे unit चालू नाही आणि (13: Permission denied) म्हणजे socket अस्तित्वात आहे, पण nginx ते उघडू शकत नाही. याचे कारण umask आणि group setting आहे.
Admin मध्ये styling दिसत नाही. collectstatic चालवलेले नाही किंवा alias path STATIC_ROOT शी जुळत नाही. Access log मध्ये /static/admin/ अंतर्गत 404 दिसतात.
कमी load असतानाही writes अपयशी होतात. SQLite मधील database is locked याचा अर्थ WAL बंद आहे किंवा एकाच वेळी लिहिणाऱ्या दोन workers साठी busy timeout खूप कमी आहे.
FAQ
Django 1 GB VPS साठी खूप जड आहे का?
नाही. कमी संख्येने workers, समोर nginx आणि मागे SQLite असलेले Django 1 GB वर आरामात चालते. त्याच मशीनवर PostgreSQL ची default settings, cache, background worker आणि Docker जोडल्यावर memory कमी पडू लागते. एका worker चा proportional set size मोजा. Request peaks साठी त्याच्या दुप्पट memory गृहीत धरा. त्यानंतर operating system आणि database वजा केल्यावर उरलेल्या memory शी एकूण वापराची तुलना करा.
एका vCPU वर किती gunicorn workers चालवावेत?
तीनपासून सुरुवात करा आणि मोजमाप करा. लहान VPS वर memory ही सहसा मुख्य मर्यादा असते. त्यामुळे operating system आणि database वजा केल्यानंतर उरलेली RAM एका worker च्या proportional set size च्या दुप्पट संख्येने भागा. तुमची views प्रामुख्याने database किंवा upstream API ची वाट पाहत असतील, तर कमी संख्येने workers आणि प्रत्येकासाठी अनेक threads असलेली gthread worker class वापरा. Threads framework ची एकच loaded copy सामायिक करतात आणि अतिरिक्त processes पेक्षा खूप कमी memory वापरतात.
मला PostgreSQL आवश्यक आहे का, की SQLite पुरेसे आहे?
एका application server साठी आणि मर्यादित write rate असल्यास SQLite पुरेसे आहे. तसेच ते memory budget मधून पूर्ण daemon वगळते. Write ahead logging enable करा आणि busy timeout सेट करा. अन्यथा concurrent writes database is locked मुळे अपयशी ठरतात. एकापेक्षा जास्त machines कडून write करणे आवश्यक झाल्यावर PostgreSQL कडे जा. Concurrent heavy writers किंवा per-role access control यांसारखी SQLite मध्ये नसलेली सुविधा आवश्यक असल्यासही PostgreSQL वापरा.
gunicorn ऐवजी uvicorn चालवावे का?
तुमच्याकडे async views आणि प्रतीक्षा करण्यासाठी प्रत्यक्ष asynchronous काम असेल तरच. Flask हे WSGI application आहे. त्यामुळे async view worker thread मध्ये नव्या event loop मध्ये चालते आणि पुढील request सुरू होण्यापूर्वी पूर्ण होते. त्यामुळे अतिरिक्त concurrency मिळत नाही. Django async views साठीही उपयुक्त ठरण्यासाठी ASGI server आवश्यक आहे. अलीकडील uvicorn releases मध्ये त्यांची gunicorn worker class स्वतंत्र package मध्ये हलवली आहे. त्यामुळे जुन्या tutorial मधील जुना worker class flag तसाच वापरण्याऐवजी सध्याचे uvicorn documentation वाचा.
कोणताही traceback न दाखवता माझा worker का गायब झाला?
Kernel च्या out of memory killer ने process बंद केल्यास त्याला SIGKILL मिळतो. बंद होताना तो कोणतीही नोंद log करू शकत नाही. त्यामुळे application log अचानक थांबतो. Gunicorn ही तफावत ओळखतो आणि Worker (pid:1234) was sent SIGKILL! Perhaps out of memory? छापतो. dmesg -T | grep -i "killed process" वापरून याची खात्री करा. उपाय म्हणजे workers ची संख्या कमी करणे किंवा swapfile तयार करणे. त्यामुळे memory spike मुळे process बंद होण्याऐवजी request मंद होईल.