SSD Nodes Learn 🎉 VPS החל מ־$5.50/חודש
מדריכים Matt Connorמאת Matt Connor · עודכן 2026-08-13

Django מול Flask בשרת VPS קטן: השוואת צריכת זיכרון

מה העלות האמיתית של Django ו-Flask בשרת עם 1GB עד 2GB RAM? בדקנו את צריכת הזיכרון של כל gunicorn worker וחישבנו כמה עובדים באמת ניתן להריץ על שרת קטן ללא קריסות.

העלות של Django ושל Flask בשרת VPS קטן

ההשוואה בין Django לבין Flask בשרת VPS קטן היא בראש ובראשונה שאלה של זיכרון. Django טוען את ה-ORM (מיפוי אובייקטי-יחסי), את מנגנון ה-migrations ואת ממשק הניהול (אם הופעל) לתוך כל תהליך עובד (worker process) שאתם מפעילים. Flask טוען נתב (router) ואובייקט בקשה בלבד. בשרת עם 1 GB זיכרון, ההבדל הזה קובע כמה עובדים ייכנסו בזיכרון, ומספר העובדים קובע כמה בקשות תוכלו לשרת בו-זמנית.

העלות הזו נחשבת כחיסרון ל-Django רק אם אתם לא משתמשים במה שהוא מספק לכם. אפליקציה עם חשבונות משתמשים, ניהול sessions ולוח בקרה מנהלי זקוקה ל-Django: ה-RAM לכל עובד הוא המחיר של קוד שלא תצטרכו לכתוב. לעומת זאת, API בפורמט JSON שיושב לפני מסד נתונים קיים מתאים יותר ל-Flask, כיוון שרוב הפיצ'רים המובנים של Django כלל לא יופעלו. זו שאלה של התאמה לצרכים. המדידות להלן יסייעו לכם להחליט לאיזה צד האפליקציה שלכם נוטה.

כמה זיכרון צורך worker אחד של gunicorn?

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
  }
]

אלו נתונים טיפוסיים שפורסמו עבור יישום "hello world" בכל תצורה על Ubuntu 24.04 עם Python 3.12, שלושה workers של gunicorn והפעלת preload. התייחסו אליהם כאל רף תחתון, שכן הייבואים (imports) שלכם מתווספים עליהם. worker של Django עם ממשק ניהול פעיל צורך 96 MB בזיכרון ה-resident, בעוד חלקו היחסי בזיכרון הוא 58 MB. הפער בין שני המספרים הללו הוא הנושא המרכזי של הסעיף הבא.

בצעו את אותה מדידה על השרת שלכם.

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

התקינו את setproctitle. כאשר הוא מותקן, gunicorn משנה את שמות התהליכים שלו ל-gunicorn: master [site1] ו-gunicorn: worker [site1], מה שמאפשר לפקודות הבאות למצוא את ה-workers לפי שם במקום לנחש.

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

העמודה rss מייצגת את ה-resident set size בקילובייטים: כל דף זיכרון שהתהליך מחזיק כרגע ב-RAM. חיבור הערכים של כל ה-workers יניב מספר גבוה מדי, כיוון ש-worker שנוצר ב-fork חולק דפים עם תהליך האב ועם תהליכי האחות שלו, כך שאותו דף נספר כמה פעמים. בקשו מה-kernel את ה-proportional set size (PSS), שמחלק כל דף משותף בין התהליכים שממפים אותו.

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

הריצו זאת כמשתמש שבבעלותו ה-workers, או באמצעות sudo. ה-PSS היא העמודה לפיה יש לתכנן את התקציב, כיוון ש-PSS מסתכם בצורה נכונה בעוד RSS לא.

Django גדול יותר בגלל מה ש-django.setup() עושה. הוא מייבא כל רשומה ב-INSTALLED_APPS, בונה את ה-application registry, ומאתחל כל מחלקת מודל יחד עם אובייקט Python עבור כל שדה בו. הוספת django.contrib.admin מפעילה את ה-admin autodiscovery, שמייבא את מודול ה-admin של כל אפליקציה וגורר אחריו את שכבות ה-forms וה-template. worker של Flask מייבא את Werkzeug ו-Jinja2, ועוצר שם.

הסתייגות אחת כנה: ה-framework הוא לעיתים קרובות החלק הקטן. worker שמייבא SDK של ענן או כל ספרייה נומרית כבדה, נושא בנטל גדול יותר מאשר Django. מדדו את היישום האמיתי שלכם לפני שתחליטו שה-framework הוא הבעיה.

Copy on write, ומדוע preload משנה את המספר

תהליך ה-master של Gunicorn מבצע fork לתהליכי ה-worker. מיד לאחר fork(), התהליך הבן חולק כל דף זיכרון עם תהליך האב, וה-kernel מעתיק דף רק כאשר אחד הצדדים כותב אליו. לכן, השאלה אם ה-model registry של Django קיים פעם אחת או ארבע פעמים בשרת תלויה בשלב שבו הוא נוצר ביחס ל-fork.

כאשר preload_app כבוי, כל worker מייבא את היישום שלך לאחר ה-fork, ולכן כל אחד בונה עותק פרטי משלו. כאשר הוא מופעל, ה-master מייבא את היישום פעם אחת וה-workers יורשים את הדפים הללו.

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. כל header של אובייקט מכיל מונה הפניות (reference count), ונגיעה באובייקט כותבת ל-header הזה; לכן, דפים משותפים מועתקים בחזרה אחד אחד בזמן שה-garbage collector סורק את ה-heap. gc.freeze() מעביר את כל מה שהוקצה עד כה לדור קבוע (permanent generation) שה-collector אינו סורק עוד, מה ששומר על יותר דפים במצב משותף. when_ready הוא ה-hook הנכון מכיוון שהוא רץ לאחר ה-preload ולפני ה-fork של ה-worker הראשון. מדדו את ה-PSS לפני ואחרי הוספתו, שכן החיסכון תלוי בכמות המצב (state) של היישום שלכם שנוצר בזמן ה-import.

ל-preload יש עלות אחת שמפתיעה אנשים ביום ה-deploy. systemctl reload שולח HUP, וההתנהגות המתועדת של Gunicorn ב-HUP היא לטעון מחדש את התצורה ולהפעיל workers חדשים. כאשר היישום נטען מראש (preloaded), הוא לא מבצע import מחדש לקוד שלכם, ולכן הגרסה החדשה לא רצה למרות שתהליכי ה-worker חדשים. השתמשו ב-systemctl restart לאחר שינוי קוד, או ברצף של USR2 ואז WINCH אם אתם צריכים שה-workers הישנים יסיימו את עבודתם קודם.

כמה עובדים (workers) יכול VPS עם 1 GB RAM להריץ באמת?

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
  }
]

אלו נתונים עבור מצב סרק בשרת שאינו משרת דבר. נותרו כ-550 MB עבור עובדי היישום, וזה עוד לפני שהבקשה הראשונה הגיעה.

כעת בצעו חלוקה, ועשו זאת בצורה פסימית. בקשה צורכת זיכרון בזמן הריצה שלה: למשל, שאילתה הטוענת כמה אלפי שורות ולאחריה רינדור תבנית. צריכת השיא לכל עובד היא לרוב קרובה לכפול מצריכת הסרק, לכן תכננו לפי כפול. Django עם ממשק הניהול ב-58 MB במצב סרק יאפשר לכם ארבעה עובדים על שרת זה. Flask עם SQLAlchemy ב-38 MB יאפשר לכם שבעה.

ההמלצה של (2 x cores) + 1 עבור Gunicorn מניחה ש-CPU הוא המשאב המוגבל ו-RAM אינו כזה. ב-VPS קטן, המצב הפוך. vCPU משותף אחד גם נותן לכם פחות מכוח עיבוד של ליבה אחת כאשר המארח עמוס, דבר שכדאי להבין לפני שמאשימים את הקוד שלכם: זמן גניבת CPU משכן רועש מופיע ב-top כנתון ה-st.

אם ה-views שלכם בעיקר ממתינים למסד נתונים או ל-API חיצוני, threads עדיפים כאן על פני תהליכים (processes). --worker-class gthread --workers 2 --threads 4 מספק שמונה בקשות במקביל בעלות זיכרון של שני עובדים, מכיוון ש-threads חולקים עותק אחד טעון של המפרש (interpreter) וה-framework. ה-global interpreter lock אומר ש-threads לא יעזרו ל-view שצורך הרבה CPU.

הוסיפו swap לשרת. VPS עם 1 GB ללא swap יהפוך קפיצה בזיכרון לתהליך שנסגר (killed), בעוד ש-swapfile יהפוך את אותה קפיצה לבקשה איטית בלבד.

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

לאחר מכן, הגבילו את היישום עצמו. MemoryMax=600M ביחידת ה-gunicorn אומר שה-kernel יחזיר את הזיכרון מה-cgroup של היישום שלכם במקום לבחור קורבן מכל השרת, כך שבקשה שיוצאת משליטה לא תעלה לכם בניתוק ה-SSH שלכם.

התנהגות בעת הפעלה קרה ואתחול

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
  }
]

עלות ההפעלה משולמת פעמיים: בכל פריסה (deploy), ובכל אתחול אוטומטי לאחר קריסה. יישום Flask מינימלי מוכן תוך כ-90 מילי-שניות, ויישום Django עם ממשק ניהול פעיל דורש כ-720 מילי-שניות על אותו vCPU משותף. אלו נתונים אופייניים לפרסום. מדדו את היישום שלכם, שכן התלויות (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

השורות האחרונות מציגות את הייבואים (imports) האיטיים ביותר במיקרו-שניות מצטברות. עבור Flask, הריצו את אותו דגל מול המודול שלכם: python -X importtime -c "import app".

כאשר preload מופעל, תהליך ה-master משלם את העלות הזו פעם אחת, וכל worker שנוצר באמצעות fork מתחיל באופן מיידי. כאשר preload כבוי, כל worker משלם את העלות הזו, והדגל timeout של gunicorn מכסה גם את ההפעלה וגם את הבקשה. worker שלא ביצע check-in בתוך timeout שניות נקטל ומוחלף; לכן, יישום כבד על vCPU משותף ואיטי עלול להיתקע בלולאת אתחול שלעולם לא מגישה בקשות. הלוג מציג:

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

מיגרציות (migrations) צריכות להתבצע ב-unit, ולא בקוד ההפעלה של היישום. ExecStartPre רץ פעם אחת לפני שקיים worker כלשהו. הרצת migrate בתוך היישום שלכם גורמת לשלושה workers להתחרות זה בזה מול אותו מנעול סכימה (schema lock).

מבנה הפריסה כמעט זהה

מנהל תהליכים

שתי המסגרות רצות תחת 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 תמיד קיים עם הבעלים הנכון. השורה umask = 0o007 בתצורת gunicorn היא זו שמאפשרת לקבוצה www-data לכתוב ל־socket, וזו הדרך שבה nginx ניגש אליו.

יחידת ה־Flask היא אותו קובץ עם שינוי בשורה אחת: ExecStart=... gunicorn -c /srv/site1/gunicorn.conf.py app:app, ללא ExecStartPre. הארגומנט app:app הוא מודול ואחריו ה־callable, לכן השגיאה Failed to find attribute 'app' in 'app'. מציינת שהמודול שלך אינו מגדיר משתנה בשם זה. משימות מתוזמנות עוקבות אחר אותו דפוס, ו־טיימר של systemd מחליף את cron עבור פקודת ניהול של Django מבלי להוסיף תור משימות לשרת בסדר גודל כזה.

קבצים סטטיים

Django עם DEBUG = False אינו מגיש קבצים סטטיים כלל. הגדירו STATIC_ROOT, הריצו את python manage.py collectstatic, והפנו את שרת האינטרנט לספריית הפלט. אם תדלגו על שלב זה, ממשק הניהול ייטען ללא עיצוב והלוג יתמלא ב־Not Found: /static/admin/css/base.css.

ישנן שתי דרכים סבירות להגשתם. בלוק alias ב־nginx אינו גוזל משאבים מהיישום. WhiteNoise, שנוסף כ־middleware, מגיש קבצים מה־worker וחוסך את בלוק ה־nginx, במחיר של מעט זמן עבודה של ה־worker לכל קובץ. Flask מגיש את תיקיית ה־static/ שלו בסביבת פיתוח, ובסביבת ייצור מפנים את ה־proxy לתיקייה מאותה סיבה.

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 שהבקשה המקורית הייתה HTTPS, אחרת בדיקות ה־cross site request forgery (CSRF) ידחו את הטפסים שלך.

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

אם השרת כבר מריץ מכולות, Traefik לפני כמה יישומי Docker Compose מבצע את אותה עבודה באמצעות תוויות (labels) על המכולה במקום קובץ אחד לכל אתר.

איזה מסד נתונים לבחור

SQLite מתאים בהחלט לשרת יישומים יחיד עם קצב כתיבה של כמה פעולות בשנייה, והוא חוסך daemon שלם מתקציב הזיכרון. הפעילו write ahead logging (WAL) והגדירו לדרייבר busy timeout.

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

ללא שתי האפשרויות הללו תיתקלו ב־django.db.utils.OperationalError: database is locked בפעם הראשונה ששני workers יכתבו בו-זמנית, כיוון שמצב ה-journal המוגדר כברירת מחדל חוסם קוראים בזמן כתיבה, וזמן ההמתנה המוגדר כברירת מחדל מוותר כמעט מיד. הדיון המורחב, כולל הנקודה שבה SQLite מפסיק להיות הפתרון הנכון, נמצא ב־הרצת SQLite בסביבת ייצור על גבי VPS.

PostgreSQL על אותו שרת של 1 GB עולה 120 MB בתקציב שצוין לעיל, בתוספת תהליך backend אחד לכל חיבור קבוע. ה־CONN_MAX_AGE של Django מחזיק חיבור פתוח לכל worker, כך שארבעה workers משמעותם ארבעה תהליכי backend. בדרך כלל זו עסקה משתלמת. פשוט חשבו זאת לפני קביעת מספר ה־workers. אם אתם מעדיפים להחזיק את מסד הנתונים במכולה לצד היישום, הרצת Docker על גבי VPS היא אותה עסקה ברף גבוה יותר, שכן ה־daemon וכל מכולה מוסיפים תקורה שמשמעותית בסדר גודל כזה.

מה מקבלים ומה המחיר

הנפח הנוסף שצורך Django מורכב מרשימת רכיבים קיימים שעובדים יחד: ה-ORM עם מנגנון ה-migrations, מערכת ה-session והאימות, מודל ההרשאות, שכבת ה-forms עם הגנת CSRF, מנוע ה-templates, פקודות ה-management וה-admin. ה-admin הוא הרכיב שאנשים נוטים להמעיט בערכו. מדובר בעורך מסד נתונים פעיל עבור המודלים שלכם, הכולל חיפוש וסינון, וכל זאת בשורת קוד אחת ב-INSTALLED_APPS.

Flask הוא תמונת המראה. אתם מקבלים ניתוב (routing), אובייקט request, תבניות Jinja2 ואובייקט הגדרות (config). כל השאר הוא בחירה שלכם, וזהו יתרון אמיתי כשהיישום קטן, שכן שום ORM לא נטען אם מעולם לא ייבאתם אחד כזה.

המלכודת טמונה בדרך האמצע. אם תוסיפו SQLAlchemy למודלים, Alembic ל-migrations, Flask-Login ל-sessions, Flask-WTF ל-forms ו-CSRF, ותוסף admin לניהול אחורי, הרכבתם משהו עם פרופיל זיכרון דומה לזה של Django, אך ללא הלכידות שלו. לכל רכיב יש מחזור שחרור גרסאות משלו ודעה משלו על האופן שבו היישום צריך להיות מחובר. זו הנקודה שבה Django הופך לפתרון הזול יותר, הן במונחי RAM והן בשעות שתשקיעו בשדרוגים.

Django לעומת Flask: כלל ההחלטה

השתמשו ב-Django כאשר ליישום יש חשבונות משתמשים, תוכן הניתן לעריכה, סכימת בסיס נתונים שעתידה להשתנות, וממשק ניהול (back office) שמישהו אכן יפתח. השתמשו ב-Flask כאשר היישום הוא ממשק JSON מעל מאגר נתונים קיים, או מקלט webhook ללא רכיבי HTML.

שובר השוויון הוא רשימה כתובה. רשמו כל חבילה שתצטרכו להתקין ב-Flask כדי להגיע למערך התכונות הנדרש. אם הרשימה כוללת ORM וכלי לניהול מיגרציות, כבר בחרתם ב-Django ואתם משלמים מחיר נוסף כדי להגיע לשם באיטיות.

מקרה אחד באמת מצדיק שימוש ב-Flask על חומרה דלה: כמה שירותים קטנים על שרת אחד. כל שירות Flask הוא תהליך זול תחת יחידת systemd משלו. שלושה אתרי Django על שרת VPS של 1 GB משמעותם שלושה עותקים של ה-framework שרצים בו-זמנית, והחישוב לעיל מפסיק לעבוד. אם המשאבים ממשיכים לא להספיק, שדרוג לתוכנית גדולה יותר הוא לרוב הפתרון הכנה, ו-מה העלות האמיתית של VPS לחודש היא שיחה קצרה יותר מאשר כתיבה מחדש של יישום עובד.

מצבי כשל והודעות שגיאה נפוצות

עובדים (workers) נעלמים וחוזרים. Gunicorn מדפיס את [ERROR] Worker (pid:1234) was sent SIGKILL! Perhaps out of memory?. הודעה זו מופיעה גם כאשר התהליך הורג עובד שחרג מזמן ה-heartbeat שלו, וגם כאשר ה-kernel הרג את התהליך. ניתן להבחין ביניהם באמצעות dmesg -T | grep -i "killed process". שורה שם מעידה על מחסור בזיכרון, לכן יש להקטין את מספר העובדים או להוסיף swap.

כל דף מחזיר 400 והלוג מציג Invalid HTTP_HOST header. ההודעה המלאה היא Invalid HTTP_HOST header: 'example.com'. You may need to add 'example.com' to ALLOWED_HOSTS.. Django דוחה את הבקשה לפני שהיא מגיעה לקוד שלכם, כיוון ש-ALLOWED_HOSTS ריק או אינו כולל את השם שה-proxy העביר ב-Host.

טפסים נכשלים עם Origin checking failed. הדף מציג שגיאת אימות CSRF. זה קורה מאחורי proxy שמבצע TLS termination: היישום רואה HTTP רגיל, בונה מקור (origin) מסוג http://, ומשווה אותו לבקשה שהגיעה דרך https://. הגדירו את SECURE_PROXY_SSL_HEADER ו-CSRF_TRUSTED_ORIGINS, וודאו שה-proxy אכן שולח את X-Forwarded-Proto.

nginx מחזיר 502 באופן מיידי. לוג השגיאות מציין את הסיבה: connect() to unix:/run/site1/gunicorn.sock failed (2: No such file or directory) משמעו שהיחידה אינה רצה, ו-(13: Permission denied) משמעו שה-socket קיים אך nginx אינו יכול לפתוח אותו, מה שמעיד על הגדרות umask והקבוצה.

לממשק הניהול אין עיצוב (styling). הפקודה collectstatic לא הורצה, או שהנתיב alias אינו תואם ל-STATIC_ROOT. לוג הגישה מציג שגיאות 404 תחת /static/admin/.

פעולות כתיבה נכשלות תחת עומס קל. השגיאה database is locked מ-SQLite מעידה על כך ש-WAL כבוי או שזמן ה-busy timeout קצר מדי עבור שני עובדים שמנסים לכתוב בו-זמנית.

FAQ

האם Django כבד מדי עבור VPS עם 1 GB RAM?

לא. Django עם מספר קטן של workers, שרת Nginx לפניו ו־SQLite מאחוריו פועל בנוחות על 1 GB. המצב הופך ללחוץ כאשר מוסיפים לאותו שרת את PostgreSQL עם הגדרות ברירת המחדל שלו, שירות cache, worker למשימות רקע ו־Docker. מדדו את ה-proportional set size של worker אחד, הכפילו אותו כדי לאפשר עומס בשיא הבקשות, ובדקו את הסך הכולל מול הזיכרון שנותר לאחר הקצאת המשאבים למערכת ההפעלה ולמסד הנתונים.

כמה gunicorn workers כדאי להריץ על vCPU אחד?

התחילו בשלושה ובצעו מדידות. זיכרון הוא בדרך כלל המגבלה בשרתי VPS קטנים, לכן חלקו את ה-RAM שנותר לאחר מערכת ההפעלה ומסד הנתונים בערך הכפול מה-proportional set size של worker בודד. אם ה-views שלכם ממתינים בעיקר למסד נתונים או ל-API חיצוני, עברו למחלקת ה-worker מסוג gthread עם מספר קטן של workers ומספר threads לכל אחד, שכן threads חולקים עותק טעון אחד של ה-framework וצורכים הרבה פחות זיכרון מאשר תהליכים נפרדים.

האם אני זקוק ל-PostgreSQL, או ש-SQLite מספיק?

SQLite מספיק עבור שרת יישומים אחד עם קצב כתיבה מתון, והוא חוסך את הצורך להריץ daemon שלם בזיכרון. הפעילו write ahead logging והגדירו busy timeout, אחרת כתיבות בו-זמניות ייכשלו עם database is locked. עברו ל-PostgreSQL כאשר יותר ממכונה אחת צריכה לכתוב למסד הנתונים, או כאשר אתם זקוקים לפיצ'רים ש-SQLite אינו מציע, כגון כותבים כבדים בו-זמנית או בקרת גישה לפי תפקידים.

האם כדאי להריץ uvicorn במקום gunicorn?

רק אם יש לכם async views ומשהו ממשי להמתין לו. Flask הוא יישום WSGI, לכן async view רץ בתוך event loop חדש בתוך ה-thread של ה-worker ומסתיים לפני שהבקשה הבאה מתחילה, מה שלא מעניק יתרון ב-concurrency. ה-async views של Django זקוקים לשרת ASGI כדי להפיק תועלת כלשהי. גרסאות עדכניות של uvicorn העבירו את מחלקת ה-gunicorn worker שלהן לחבילה נפרדת, לכן קראו את התיעוד העדכני של uvicorn במקום להעתיק flag של מחלקת worker ממדריך ישן.

מדוע ה-worker שלי נעלם ללא traceback?

תהליך שנהרג על ידי ה-kernel באמצעות ה-out of memory killer מקבל SIGKILL ואינו יכול לרשום דבר ביומן לפני סגירתו, לכן יומן היישום פשוט נעצר. Gunicorn מבחין בחסר ומדפיס Worker (pid:1234) was sent SIGKILL! Perhaps out of memory?. אשרו זאת באמצעות dmesg -T | grep -i "killed process". הפתרון הוא הפחתת מספר ה-workers, או הוספת swapfile כך שקפיצה בזיכרון תגרום לבקשה איטית במקום למות תהליך.

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