# הרחבת קיבולת

> הרחיבו את Nodaro באירוח עצמי מעבר לקונטיינר אחד: פצלו את ה-API, את ה-workers ואת ה-orchestrator, וכווננו את המקביליות, את Redis ואת האחסון.

Source: https://nodaro.ai/he/docs/self-hosting/scaling

**הרחבת הקיבולת** של Nodaro באירוח עצמי פירושה הרצה של הפרוססים שלו ביותר מקונטיינר אחד. במבנה ברירת המחדל של Compose, הכול רץ בקונטיינר אחד: ה-API, ה-workers, ה-orchestrator ושרת הווב. המבנה הזה מתאים לעד כ-5 משתמשים פעילים. ליותר משתמשים, הריצו את ה-workers בקונטיינרים משלהם. כל הקונטיינרים חולקים Redis אחד, מסד נתונים אחד ו-bucket אחסון אחד, והתיאום ביניהם נעשה רק דרך התורים ב-Redis.

## הפרוססים
סקריפט ההפעלה של האימג׳, `/app/start.sh`, מפעיל את הפרוססים האלה זה לצד זה, מתוך `/app/backend`:

| פקודה | מה היא עושה | עומס |
| --- | --- | --- |
| `node dist/server.js` | ה-HTTP API | צריכת CPU נמוכה, צריכת זיכרון בינונית |
| `node dist/worker.js` | ה-worker של המדיה: משימה אחת לכל רכיב, קורא לספקי המודלים | ממתין לרשת, מריץ משימות רבות במקביל |
| `node dist/render-worker.js` | ה-worker של הרינדור: קומפוזיציות, ב-Chrome ללא ממשק | ה-CPU מגביל אותו: אחד או שניים לכל מכונה |
| `node dist/orchestrator.js` | ה-orchestrator של התהליכים: מריץ את הגרף של כל תהליך | ממתין לרשת, צריכת CPU נמוכה |
| `node dist/pipeline-worker.js` | הפייפליין **סיפור ← וידאו**, שרץ רק ב-Nodaro Cloud | נסגר מיד במהדורות באירוח עצמי |

סקריפט ההפעלה עושה גם ארבעה דברים שקונטיינר שמריץ פרוסס אחד לא עושה:

- הוא מריץ את שרת הווב בפורט `3000`, לפני ה-API.
- הוא מחיל את המיגרציות במערך המובנה.
- הוא יוצר את `INTERNAL_ORCHESTRATOR_SECRET` כשהמשתנה לא מוגדר.
- הוא יוצר את מפתח ההצפנה במערך המובנה.

עברו על הסקריפט שבאימג׳ לפני שאתם מפצלים אותו.

## פיצול טיפוסי
- **קונטיינר API אחד**, שמריץ את `server.js`.
- **כמה קונטיינרים ל-worker של המדיה**. הערך `VIDEO_WORKER_CONCURRENCY=50`, ברירת המחדל, מתאים לכל אחד מהם.
- **קונטיינר אחד או שניים ל-worker של הרינדור**, כל אחד במכונה משלו.
- **קונטיינר orchestrator אחד**.

הקונטיינרים אף פעם לא מתקשרים זה עם זה ישירות. כולם משתמשים באותו Redis, באותו Supabase ובאותו אחסון, ו-Redis הוא נקודת התיאום היחידה.

## מה כל הקונטיינרים חייבים לחלוק
| משתנה | למה הוא חייב להיות זהה |
| --- | --- |
| `INTERNAL_ORCHESTRATOR_SECRET` | ה-orchestrator מזדהה מול ה-API באמצעותו. כשהמשתנה לא מוגדר, סקריפט ההפעלה יוצר ערך חדש בכל קונטיינר, ולכן הגדירו אותו במפורש, לאותו ערך בכל מקום. |
| `NODARO_ENCRYPTION_KEY` | מפתחות הספק ופרטי הגישה השמורים מוצפנים בו. מפתח אחר לא יכול לקרוא אותם. |
| `RUNTIME_ENV` | נותן שם להתקנה. כל הקונטיינרים של התקנה אחת חייבים להשתמש באותו ערך. |
| `EDITION`, `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, `REDIS_URL` והמשתנים `R2_*` | אותה מהדורה, אותו מסד נתונים, אותם תורים ואותו אחסון בכל קונטיינר. |

## מקביליות
| משתנה | ברירת מחדל | מה הוא מגביל |
| --- | --- | --- |
| `MAX_CONCURRENT_NODES_PER_EXECUTION` | `6`, לכל היותר `20` | כמה רכיבים הרצה אחת של תהליך יכולה להריץ במקביל. זו התקרה של השרת כולו לרכיבים מקבילים. |
| `VIDEO_WORKER_CONCURRENCY` | `50` | משימות מקבילות ב-worker אחד של המדיה |
| `ORCHESTRATOR_CONCURRENCY` | `20` | משימות תהליך מקבילות ב-orchestrator אחד |
| `RENDER_WORKER_CONCURRENCY` | `2`, לכל היותר `10` | רינדורים מקבילים ב-worker אחד של הרינדור. כל רינדור רץ ב-Chrome ללא ממשק משלו. |
| `REMOTION_CONCURRENCY` | `2` לסצנות תלת־ממד; מחצית מליבות ה-CPU לרינדורים אחרים | כרטיסיות דפדפן לכל רינדור. השאירו אותו נמוך כשרצות כמה משימות תלת־ממד: כל כרטיסיית WebGL מוסיפה threads ונספרת במגבלת הפרוססים של הקונטיינר. |
| `FFMPEG_CONCURRENCY` | `4`, לכל היותר `32` | מספר פרוססי ffmpeg בו־זמנית, בכל רכיבי עריכת הווידאו והאודיו יחד |

קובץ ה-Compose לא מעביר את המשתנים האלה מהקובץ `.env`. הוסיפו אותם תחת `environment:` של כל שירות שצריך אותם.

כל רכיב בהרצה יכול לרוץ עד 90 דקות, והרצה שלמה עד 120 דקות. רכיב שמצהיר על תקציב זמן משלו מקבל את התקציב הזה במקום, ומגבלת ההרצה גדלה באותה מידה. רכיב [החלת רשימת עריכה](https://nodaro.ai/docs/nodes/video/apply-edl) הוא רכיב כזה: התקציב שלו נקבע לפי העריכה שהוא מרנדר.

## זמינות גבוהה ל-Redis
תורי המשימות תומכים במצב האשכול של Redis. הגדירו את `REDIS_URL` לנקודת קצה של אשכול או לכתובת URL של Sentinel.

מלבד התורים, ה-API שומר ב-Redis מטמונים משותפים קטנים, כדי שכמה קונטיינרים של API לא יחזרו כל אחד על אותה עבודה איטית מול ספק. כיום מדובר בקטלוג האווטארים והקולות של HeyGen, בגודל של כ-4 MB. קונטיינר אחד מרענן אותו תחת נעילה כל `HEYGEN_CATALOG_REFRESH_HOURS` שעות, 24 כברירת מחדל, והקונטיינרים האחרים עוברים לעותק החדש בתוך כחצי דקה. כל מה שנשמר שם הוא מטמון: כש-Redis לא זמין, כל קונטיינר משתמש בזיכרון שלו, ורשומה שאבדה מתמלאת שוב מהספק באתחול הבא.

## שתי התקנות, מסד נתונים אחד
אפשר לכוון התקנה שנייה, כמו עותק staging, אל **אותו** פרויקט Supabase, עם Redis **משלה**. במקרה כזה, תנו לכל התקנה ערך `RUNTIME_ENV` שונה. ב-Railway, המשתנה `RAILWAY_ENVIRONMENT_NAME` כבר עושה את זה.

כל הרצה רושמת את שם ההתקנה שה-orchestrator שלה קיבל אותה לטיפול, ופעולת הניקוי של כל התקנה בודקת רק את ההרצות שלה. בלי שמות שונים, כל התקנה מחפשת ב-Redis שלה את המשימות של ההתקנה האחרת, לא מוצאת אותן ומסמנת הרצות תקינות כנכשלות עם `Execution orphaned` (הרצה יתומה). להרצות שהתחילו לפני שההתקנות התחילו לרשום את השמות שלהן אין שם; ההתקנה בשם `production` מטפלת בהן.

## מחזור החיים של האחסון
Nodaro אף פעם לא מוחק מעצמו מדיה שמורה: הוא רק מצביע על קבצים לפי המפתח שלהם. כדי שמדיה ישנה תימחק אוטומטית, הוסיפו ל-bucket שלכם כלל מחזור חיים, למשל אחרי 90 ימים. כללו בכלל את הקידומת `video-analysis-tmp/`: יש בה קבצי ניתוח זמניים, ולהתקנות באירוח עצמי אין משימת ניקוי שמטפלת בהם.

## עצירת קונטיינרים ב-Railway
ב-Railway, המשתנה `RAILWAY_DEPLOYMENT_DRAINING_SECONDS` קובע את הזמן בין אות העצירה לעצירה הכפויה של קונטיינר שמוחלף. ה-worker של המדיה ממשיך לסיים את המשימות שבטיפולו במשך הזמן הזה פחות 5 שניות, כך שקריאה ארוכה למודל יכולה להסתיים ולהישמר לפני שהמשימה שלה עוברת לקונטיינר החדש. כשהמשתנה לא מוגדר, ה-worker ממשיך לסיים את המשימות שבטיפולו במשך 25 שניות.

## שאלות נפוצות

### כמה משתמשים קונטיינר אחד של Nodaro יכול לשרת?

מבנה ברירת המחדל של קונטיינר אחד מתאים לעד כ-5 משתמשים פעילים. מעבר לזה, הריצו את ה-workers של המדיה, את ה-workers של הרינדור ואת ה-orchestrator בקונטיינרים נפרדים.

### איך הקונטיינרים של Nodaro מתקשרים זה עם זה?

הקונטיינרים של Nodaro לא מתקשרים ישירות. כל קונטיינר מתחבר לאותו Redis, לאותו מסד נתונים ולאותו אחסון, ותורי המשימות ב-Redis הם נקודת התיאום היחידה.

### למה ההרצות שלי מסומנות "Execution orphaned" (הרצה יתומה)?

כנראה ששתי התקנות של Nodaro חולקות מסד נתונים אחד, אבל משתמשות במופעי Redis נפרדים בלי שמות שונים. תנו לכל התקנה ערך RUNTIME_ENV משלה, והשתמשו באותו ערך בכל הקונטיינרים של התקנה אחת.

### האם Nodaro מוחק מדיה ישנה מה-bucket שלי?

לא. Nodaro רק מצביע על המדיה השמורה ואף פעם לא מוחק אותה מעצמו. כדי שקבצים ישנים יימחקו אוטומטית, הוסיפו ל-bucket שלכם כלל מחזור חיים, וכללו בו את הקידומת video-analysis-tmp/‎.
