ছোট VPS-এ Django বনাম Flask: কোনটি কম RAM খরচ করে?
1 থেকে 2 GB VPS-এ Django ও Flask-এর আসল খরচ দেখুন: gunicorn worker-পিছু resident memory, এবং ছোট সার্ভারে বাস্তবে কত worker চালানো যায়।
একটি ছোট VPS-এ Django এবং Flask-এর খরচ
একটি ছোট VPS-এ Django বনাম Flask বেছে নেওয়ার ক্ষেত্রে প্রথম প্রশ্নটি হলো মেমরি। Django চালু করা প্রতিটি worker process-এ তার object relational mapper (ORM), migration ব্যবস্থা এবং আপনি সক্রিয় করলে admin site লোড করে। Flask একটি router এবং request object লোড করে। 1 GB-এর সার্ভারে এই পার্থক্য নির্ধারণ করে কতগুলো worker চালানো যাবে। worker-এর সংখ্যা একই সময়ে কতগুলো request পরিবেশন করা যাবে তা নির্ধারণ করে।
Django যে সুবিধাগুলো দেয়, আপনি সেগুলো নতুন করে তৈরি না করলে এই খরচটি শুধু Django-এর অতিরিক্ত খরচ হিসেবে গণ্য হয়। user account, session এবং admin panel-সহ একটি অ্যাপের জন্য Django উপযুক্ত। প্রতি worker-এর RAM ব্যবহার এমন code-এর মূল্য, যা আপনাকে নিজে লিখতে হবে না। আপনি আগে থেকেই চালু রাখা datastore-এর সামনে একটি JSON API চাইলে Flask উপযুক্ত। কারণ সেখানে Django-এর অধিকাংশ built-in সুবিধা কখনোই লোড হবে না। এটি উপযুক্ততার প্রশ্ন। নিচের পরিমাপগুলো দেখাবে আপনার অ্যাপ কোন দিকের সঙ্গে বেশি মানানসই।
একজন gunicorn worker কত মেমরি ব্যবহার করে?
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 worker এবং preload চালু থাকা অবস্থায় প্রতিটি ধরনের hello world অ্যাপের জন্য এগুলো সাধারণ প্রকাশিত পরিমাপ। এগুলোকে সর্বনিম্ন মান হিসেবে ধরুন, কারণ আপনার নিজস্ব import-এর মেমরি ব্যবহার এর সঙ্গে যোগ হবে। admin চালু থাকা একটি Django worker-এর resident memory 96 MB, আর মেমরির 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 ইনস্টল করুন। এটি ইনস্টল থাকলে gunicorn তার process-গুলোর নাম gunicorn: master [site1] এবং gunicorn: worker [site1]-এ পরিবর্তন করে। ফলে পরের command-গুলো অনুমান করে নয়, নাম দেখে worker খুঁজে পায়।
pgrep -af gunicorn
ps -o pid,rss,args -p $(pgrep -d, -f 'gunicorn: worker')rss column-টি kilobyte-এ resident set size: process বর্তমানে RAM-এ ধরে রাখা প্রতিটি memory page। সব worker-এর মান যোগ করলে ফল বেশি হবে, কারণ fork করা worker তার parent এবং sibling worker-গুলোর সঙ্গে page share করে। তাই একই page একাধিকবার গণনা হয়। এর পরিবর্তে kernel-এর proportional set size (PSS) নিন। PSS প্রতিটি shared page সেই page map করা process-গুলোর মধ্যে ভাগ করে গণনা করে।
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; doneworker-গুলোর মালিক user হিসেবে command-টি চালান, অথবা sudo ব্যবহার করুন। বাজেট নির্ধারণের জন্য PSS column ব্যবহার করুন, কারণ PSS সঠিকভাবে যোগ হয়, কিন্তু RSS হয় না।
django.setup()-এর কাজের কারণে Django বেশি মেমরি ব্যবহার করে। এটি INSTALLED_APPS-এর প্রতিটি entry import করে, application registry তৈরি করে এবং প্রতিটি model class-এর পাশাপাশি তার প্রতিটি field-এর জন্য একটি করে Python object তৈরি করে। django.contrib.admin যোগ করলে admin autodiscovery চালু হয়। এটি প্রতিটি app-এর admin module import করে এবং এর সঙ্গে forms ও template layer-ও memory-তে আনে। একটি Flask worker Werkzeug ও Jinja2 import করে এবং সেখানেই থামে।
একটি গুরুত্বপূর্ণ সীমাবদ্ধতা মনে রাখুন: framework প্রায়ই মোট ব্যবহারের ছোট অংশ। কোনো worker cloud SDK বা numeric library import করলে সেটি Django-এর চেয়েও বেশি মেমরি ব্যবহার করতে পারে। Framework-কে সমস্যা মনে করার আগে আপনার প্রকৃত app-এর মেমরি ব্যবহার মাপুন।
Copy-on-write এবং preload কেন memory ব্যবহারের পরিমাণ পরিবর্তন করে
Gunicorn-এর master process worker-গুলোকে fork করে। fork()-এর ঠিক পরে child process parent-এর প্রতিটি memory page share করে। কোনো এক পাশ সেই page-এ write করলেই kernel page-টির একটি copy তৈরি করে। তাই সার্ভারে Django-এর model registry একবার নাকি চারবার থাকবে, তা নির্ভর করে fork-এর কোন পাশে এটি তৈরি হয়েছে।
preload_app বন্ধ থাকলে প্রতিটি worker fork হওয়ার পরে আপনার application import করে। ফলে প্রতিটি worker নিজের private copy তৈরি করে। এটি চালু থাকলে master একবার application import করে এবং worker-গুলো সেই page inherit করে।
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 access করলে ওই header-এ write হয়। তাই garbage collector heap পরীক্ষা করার সময় shared page-গুলো একটির পর একটি copy হয়ে যায়। gc.freeze() এখন পর্যন্ত allocated সবকিছু একটি permanent generation-এ সরিয়ে দেয়, যেটি collector আর পরীক্ষা করে না। এর ফলে ওই page-গুলোর বেশি অংশ shared থাকে। when_ready সঠিক hook, কারণ এটি preload-এর পরে এবং প্রথম worker fork হওয়ার আগে চলে। এটি যোগ করার আগে ও পরে PSS মাপুন, কারণ memory সাশ্রয় নির্ভর করে import time state হিসেবে আপনার application-এর কতটা অংশ তৈরি হয় তার ওপর।
Deploy-এর সময় preload-এর একটি খরচ অনেককে অবাক করে। systemctl reload, HUP পাঠায়, এবং HUP-এ gunicorn-এর নথিভুক্ত আচরণ হলো configuration reload করে নতুন worker শুরু করা। Application preload করা থাকলে এটি আপনার code আবার import করে না। তাই worker process নতুন হলেও আপনার নতুন release চলবে না। Code পরিবর্তনের পরে systemctl restart ব্যবহার করুন। পুরোনো worker-গুলোকে আগে কাজ শেষ করার সুযোগ দিতে হলে USR2, তারপর WINCH ক্রমটি ব্যবহার করুন।
1 GB VPS-এ বাস্তবে কতজন worker চালানো যায়?
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 worker-এর জন্য প্রায় 550 MB অবশিষ্ট থাকে, আর প্রথম request আসার আগেই এই হিসাব প্রযোজ্য।
এখন ভাগ করুন, তবে সতর্কতার সঙ্গে। একটি request চলার সময় memory ব্যবহার করে: কয়েক হাজার row load করা একটি queryset, এরপর template render। প্রতি worker-এর peak memory সাধারণত idle figure-এর প্রায় দ্বিগুণ হয়, তাই দ্বিগুণ ধরে হিসাব করুন। admin চালু থাকা Django-তে idle অবস্থায় প্রতি worker-এর memory 58 MB হলে এই সার্ভারে চারটি worker চালাতে পারবেন। SQLAlchemy-সহ Flask-এ প্রতি worker-এর memory 38 MB হলে সাতটি worker চালাতে পারবেন।
Gunicorn-এর (2 x cores) + 1 পরামর্শ ধরে নেয় যে CPU-ই সীমিত resource এবং RAM সীমিত নয়। ছোট VPS-এ বাস্তবতা উল্টো। host ব্যস্ত থাকলে একটি shared vCPU থেকেও একটি পূর্ণ core-এর সমপরিমাণ কাজ পাওয়া যায় না। তাই কোডকে দোষ দেওয়ার আগে বিষয়টি বুঝে নিন: noisy neighbour-এর CPU steal time top-এ st figure হিসেবে দেখা যায়।
আপনার view-গুলো যদি মূলত database বা upstream API-এর উত্তরের জন্য অপেক্ষা করে, তাহলে এখানে process-এর চেয়ে thread ভালো। --worker-class gthread --workers 2 --threads 4 memory-র দিক থেকে দুইটি worker-এর সমপরিমাণ খরচে আটটি concurrent request চালায়, কারণ thread-গুলো interpreter এবং framework-এর একটি loaded copy ভাগ করে ব্যবহার করে। global interpreter lock থাকায় CPU-intensive view-তে thread কোনো সুবিধা দেয় না।
সার্ভারে swap দিন। swap ছাড়া 1 GB VPS-এ memory spike হলে process killed হয়, আর 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-এর memory সীমিত করুন। gunicorn unit-এ MemoryMax=600M দিলে পুরো সার্ভার থেকে কোনো process বেছে নেওয়ার পরিবর্তে kernel আপনার app-এর cgroup থেকে memory ফিরিয়ে নেয়। ফলে runaway 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 cost দুইবার দিতে হয়: প্রতিটি deploy-এর সময় এবং crash-এর পরে প্রতিটি automatic restart-এর সময়। একটি minimal Flask app প্রায় 90 ms-এ প্রস্তুত হয়, আর admin enabled থাকা Django একই shared vCPU-তে প্রায় 720 ms সময় নেয়। এগুলো সাধারণত প্রকাশিত পরিমাপ। আপনার নিজের app-এর পরিমাপ নিন, কারণ মূল সময় আপনার 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-সহ সবচেয়ে ধীর import-গুলোর তালিকা দেখায়। Flask-এর জন্য আপনার module-এর ক্ষেত্রে একই flag চালান: python -X importtime -c "import app"।
preload চালু থাকলে master এই খরচ একবার বহন করে এবং প্রতিটি forked worker সঙ্গে সঙ্গে শুরু হয়। preload বন্ধ থাকলে প্রতিটি worker-কে এই খরচ বহন করতে হয়, এবং gunicorn-এর timeout boot ও request—দুই ক্ষেত্রেই প্রযোজ্য হয়। timeout সেকেন্ডের মধ্যে check in না করা worker-কে বন্ধ করে প্রতিস্থাপন করা হয়। তাই ধীর shared vCPU-তে ভারী app এমন restart loop-এ আটকে যেতে পারে, যেখানে কোনো request পরিবেশনই হয় না। Log-এ লেখা থাকে:
[2026-08-09 09:14:02 +0000] [981] [CRITICAL] WORKER TIMEOUT (pid:1004)Migration unit-এর মধ্যে রাখুন, application startup code-এ নয়। ExecStartPre কোনো worker তৈরি হওয়ার আগে একবার চলে। আপনার app-এর মধ্যে migrate রাখলে তিনটি worker একই schema lock পাওয়ার জন্য একে অপরের সঙ্গে প্রতিযোগিতা করবে।
ডিপ্লয়মেন্টের কাঠামো প্রায় একই
Process manager
উভয় framework 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.targetশুরু হলে RuntimeDirectory=site1 `/run/site1 তৈরি করে এবং বন্ধ হলে এটি সরিয়ে দেয়। ফলে socket path-টি সঠিক owner-সহ সব সময় বিদ্যমান থাকে। gunicorn config-এর umask = 0o007 লাইনটি 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 সংজ্ঞায়িত নেই। Scheduled work-ও একই pattern অনুসরণ করে। এই আকারের একটি server-এ task queue যোগ না করেই Django management command-এর জন্য systemd timer cron-এর পরিবর্তে ব্যবহার করা যায়।
Static files
`DEBUG = False সহ Django কোনো static file পরিবেশন করে না। STATIC_ROOT সেট করুন, python manage.py collectstatic চালান, এবং web server-কে output directory-তে নির্দেশ করুন। এই ধাপ বাদ দিলে admin interface কোনো styling ছাড়াই লোড হবে, আর log-এ Not Found: /static/admin/css/base.css` দিয়ে ভরে যাবে।
Static file পরিবেশনের দুটি যুক্তিসংগত উপায় আছে। একটি nginx `alias block আপনার app-এর কোনো resource ব্যবহার করে না। Middleware হিসেবে যোগ করা WhiteNoise worker থেকেই file পরিবেশন করে এবং nginx block-এর প্রয়োজন দূর করে। তবে প্রতিটি file পরিবেশনে worker-এর সামান্য সময় লাগে। Flask development-এ নিজের static/` folder পরিবেশন করে। Production-এ একই কারণে proxy-কে ওই folder-এ নির্দেশ করুন।
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-এর মাধ্যমে এসেছিল। তা না হলে cross site request forgery (CSRF) check আপনার নিজের form-গুলো প্রত্যাখ্যান করবে।
ALLOWED_HOSTS = ["example.com"]
CSRF_TRUSTED_ORIGINS = ["https://example.com"]
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")যদি server-এ ইতিমধ্যে container চলে, তাহলে একাধিক Docker Compose app-এর সামনে Traefik একই কাজ করে। এতে প্রতিটি site-এর জন্য একটি file-এর পরিবর্তে container-এর label ব্যবহার করা হয়।
Which database
প্রতি সেকেন্ডে কয়েকটি write-এর হারে চলা একটি 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;",
},
}
}এই দুটি option ছাড়া প্রথমবার দুইটি worker একই সময়ে write করলে `django.db.utils.OperationalError: database is locked` দেখতে পাবেন। কারণ default journal mode write চলাকালে reader-দের block করে, আর default timeout প্রায় সঙ্গে সঙ্গেই ব্যর্থ হয়। SQLite কখন আর উপযুক্ত থাকে না—এটি সহ বিস্তৃত ব্যাখ্যা VPS-এ production-এ SQLite চালানো-এ দেওয়া আছে।
একই 1 GB server-এ PostgreSQL ব্যবহার করলে উপরের budget-এ 120 MB যোগ হবে। এর সঙ্গে প্রতিটি persistent connection-এর জন্য একটি করে backend process লাগে। Django-এর `CONN_MAX_AGE` প্রতি worker-এর জন্য একটি connection খোলা রাখে। তাই চারটি worker হলে চারটি backend থাকবে। এটি সাধারণত গ্রহণযোগ্য বিনিময়। তবে worker সংখ্যা নির্ধারণের আগে এই খরচ হিসাব করুন। App-এর পাশে একটি container-এ database রাখতে চাইলে VPS-এ Docker চালানো একই বিনিময় দেখায়, তবে ন্যূনতম overhead বেশি। এই আকারের server-এ daemon এবং প্রতিটি container-এর অতিরিক্ত overhead গুরুত্বপূর্ণ।
কোন সুবিধার জন্য এই অতিরিক্ত মেমরি লাগে এবং এর খরচ কী
Django-এর অতিরিক্ত megabyte এমন কিছু উপাদানের জন্য লাগে, যেগুলো আগে থেকেই আছে এবং একসঙ্গে কাজ করে: migrations-সহ ORM, session ও authentication system, permission model, CSRF protection-সহ form layer, template engine, management commands এবং admin। Admin-কে মানুষ প্রায়ই কম গুরুত্ব দেয়। এটি আপনার model-এর জন্য search ও filter-সহ একটি কার্যকর database editor, এবং INSTALLED_APPS-এ মাত্র এক লাইনের configuration লাগে।
Flask-এর চিত্রটি এর বিপরীত। আপনি routing, request object, Jinja2 templates এবং config object পান। বাকি সবকিছু আপনাকেই বেছে নিতে হয়। অ্যাপ ছোট হলে এটি বাস্তব সুবিধা, কারণ কোনো ORM import না করলে সেটি load হয় না।
সমস্যা তৈরি হয় মাঝামাঝি অবস্থায়। Model-এর জন্য SQLAlchemy, migrations-এর জন্য Alembic, session-এর জন্য Flask-Login, form ও CSRF-এর জন্য Flask-WTF এবং back office-এর জন্য একটি admin extension যোগ করলে এমন একটি ব্যবস্থা তৈরি হয়, যার memory profile Django-এর মতো, কিন্তু Django-এর সমন্বিত কাঠামো নেই। প্রতিটি উপাদানের release cycle আলাদা এবং অ্যাপ কীভাবে সংযুক্ত করা উচিত সে বিষয়ে প্রত্যেকটির নিজস্ব পদ্ধতি আছে। এই পর্যায়ে Django-ই কম খরচের সমাধান—RAM-এর দিক থেকে এবং upgrade করতে আপনার যে সময় লাগে, সেই দিক থেকেও।
Django বনাম Flask: সিদ্ধান্তের নিয়ম
অ্যাপ্লিকেশনে account, সম্পাদনাযোগ্য content, সময়ের সঙ্গে পরিবর্তিত হতে থাকা schema এবং বাস্তবে কেউ ব্যবহার করবে এমন back office থাকলে Django ব্যবহার করুন। আগে থেকেই থাকা datastore-এর ওপর JSON interface তৈরি করতে হলে, অথবা HTML ছাড়া webhook receiver তৈরি করতে হলে Flask ব্যবহার করুন।
সিদ্ধান্তের ক্ষেত্রে শেষ যাচাই হলো লিখিত তালিকা। প্রয়োজনীয় feature set পেতে Flask-এ যেসব package install করবেন, সেগুলোর প্রতিটি লিখে রাখুন। সেই তালিকায় ORM এবং migration tool থাকলে, আপনি ইতিমধ্যেই Django বেছে নিয়েছেন এবং ধীরে ধীরে সেখানে পৌঁছানোর জন্য অতিরিক্ত খরচ করছেন।
ছোট hardware-এ একটি ক্ষেত্রে Flask সত্যিই বেশি উপযোগী: একই box-এ একাধিক ছোট service চালানো। প্রতিটি Flask service নিজস্ব unit-এর অধীনে একটি স্বল্প-ব্যয়ী process হিসেবে চলে। একটি 1 GB VPS-এ তিনটি Django site চালালে framework-এর তিনটি কপি একই সময়ে memory-তে থাকে, তাই উপরের হিসাব আর কার্যকর থাকে না। হিসাব বারবার প্রয়োজনের তুলনায় কম capacity দেখালে, বড় plan নেওয়াই প্রায়ই বাস্তবসম্মত সমাধান। একটি VPS-এর প্রকৃত মাসিক খরচ নিয়ে আলোচনা করা কার্যকর application নতুন করে লেখার চেয়ে সংক্ষিপ্ত।
যে ত্রুটির ধরনগুলো এবং যে বার্তাগুলো দেখবেন
Worker অদৃশ্য হয়ে আবার ফিরে আসে। 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 status ফেরত দেয় এবং 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-এ যে name পাঠিয়েছে, তা এতে নেই।
Form 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 page-এ styling নেই। collectstatic চালানো হয়নি, অথবা alias path, STATIC_ROOT-এর সঙ্গে মেলে না। Access log-এ /static/admin/-এর অধীনে 404 দেখা যায়।
কম load-এ write ব্যর্থ হয়। SQLite-এর database is locked বোঝায় যে WAL বন্ধ আছে অথবা একই সময়ে দুইটি worker write করার জন্য busy timeout খুব কম।
FAQ
1 GB VPS-এর জন্য কি Django অতিরিক্ত ভারী?
না। অল্পসংখ্যক worker, সামনে nginx এবং পেছনে SQLite থাকলে Django 1 GB RAM-এ স্বচ্ছন্দে চলে। একই server-এ PostgreSQL-এর default settings, একটি cache, একটি background worker এবং Docker যোগ করলে RAM সংকট দেখা দেয়। একটি worker-এর proportional set size মাপুন, request-এর সর্বোচ্চ চাপ সামলাতে সেটিকে দ্বিগুণ করুন, তারপর operating system ও database চালানোর পর অবশিষ্ট RAM-এর সঙ্গে মোট পরিমাণ তুলনা করুন।
একটি vCPU-তে কতগুলি gunicorn worker চালানো উচিত?
তিনটি দিয়ে শুরু করুন এবং পরিমাপ করুন। ছোট VPS-এ সাধারণত memory-ই প্রধান সীমাবদ্ধতা। তাই operating system ও database চালানোর পর অবশিষ্ট RAM-কে একটি worker-এর proportional set size-এর দ্বিগুণ দিয়ে ভাগ করুন। আপনার view-গুলো যদি মূলত database বা upstream API-এর উত্তরের জন্য অপেক্ষা করে, তাহলে অল্পসংখ্যক worker এবং প্রতিটিতে কয়েকটি thread সহ gthread worker class ব্যবহার করুন। কারণ thread-গুলো framework-এর একটি loaded copy ভাগ করে নেয় এবং অতিরিক্ত process-এর তুলনায় অনেক কম memory ব্যবহার করে।
আমার কি PostgreSQL প্রয়োজন, নাকি SQLite যথেষ্ট?
একটি application server এবং মাঝারি write rate-এর ক্ষেত্রে SQLite যথেষ্ট। এতে memory budget থেকে একটি সম্পূর্ণ daemon বাদ যায়। write ahead logging চালু করুন এবং busy timeout সেট করুন। তা না হলে concurrent write database is locked-সহ ব্যর্থ হয়। একাধিক machine-কে write করতে হলে PostgreSQL-এ যান। SQLite-এ নেই এমন সুবিধা, যেমন concurrent heavy writer বা per-role access control, প্রয়োজন হলেও PostgreSQL ব্যবহার করুন।
আমার কি gunicorn-এর পরিবর্তে uvicorn চালানো উচিত?
শুধু তখনই, যখন আপনার async view আছে এবং অপেক্ষা করার মতো বাস্তব কোনো I/O operation আছে। Flask একটি WSGI application। তাই একটি async view worker thread-এর ভেতরে নতুন event loop-এ চলে এবং পরবর্তী request শুরু হওয়ার আগে শেষ হয়। এতে অতিরিক্ত concurrency পাওয়া যায় না। Django async view কার্যকরভাবে চালাতে ASGI server প্রয়োজন। সাম্প্রতিক uvicorn release-গুলো তাদের 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" দিয়ে এটি নিশ্চিত করুন। সমাধান হলো worker-এর সংখ্যা কমানো, অথবা একটি swapfile তৈরি করা। এতে memory spike হলে process বন্ধ না হয়ে request ধীরগতিতে শেষ হতে পারে।