1. ספרייה
  2. HTTP והרשת
  3. קודי סטטוס HTTP

עודכן לפני חודש

שגיאת 504 היא אחת השגיאות המוזרות יותר ב-HTTP. לא שבור כלום. השרת במעלה הזרם עשוי לעבוד בצורה מושלמת — מעבד את הבקשה שלך, מבצע שאילתות למסדי נתונים, עושה בדיוק את מה שאמור לעשות. הגייטוויי פשוט נמאס לו לחכות.

זה מה שהופך את 504 לשונה מרוב השגיאות. זה לא עניין של כשל. זה עניין של ציפיות שאינן מסונכרנות בין מערכות.

מה באמת קורה

כשאתה רואה 504, הסיפור הוא כך:

  1. הבקשה שלך הגיעה לגייטוויי (כמו Nginx, מאזן עומסים, או CDN)
  2. הגייטוויי העביר אותה לשרת במעלה הזרם
  3. השרת במעלה הזרם התחיל לעבוד עליה
  4. סבלנות הגייטוויי אזלה לפני שהתגובה הגיעה
  5. הגייטוויי הודיע לך שפסק הזמן עבר — גם אם השרת במעלה הזרם עדיין עובד
Client → Gateway (60s timeout) → Backend (processing for 90s)
                ↓
        "I'm done waiting"
                ↓
        504 Gateway Timeout

ה-Backend מסיים 30 שניות מאוחר יותר, שולח את התגובה שלו, ו... אין מי שמקשיב.

למה הסבלנות אוזלת

השרת במעלה הזרם עושה משהו איטי. שאילתת מסד נתונים שסורקת מיליוני שורות. API חיצוני שלוקח נצח. דוח שממש צריך שתי דקות כדי להיווצר. העבודה לגיטימית — היא פשוט חורגת מפסק הזמן שהוגדר לגייטוויי.

רעב במשאבים. השרת במעלה הזרם מחכה למשהו: חיבור למסד נתונים מבריכה שאזלה, נעילה שמוחזקת על ידי תהליך אחר, זיכרון שעובר החלפה לדיסק. הוא לא עושה עבודה לאט — הוא מחכה שיוכל להתחיל.

זמן אחזור רשת. התגובה מוכנה, אבל הרשת בין הגייטוויי לשרת במעלה הזרם איטית. מנות נתונים זוחלות לאיטן. הטיימר של הגייטוויי פג לפני שהבייטים מגיעים.

עיכובים בשרשרת. ה-Backend שלך פונה לשירות אחד, שפונה לשירות אחר, שפונה למסד נתונים. כל קפיצה מוסיפה זמן אחזור. עד שהתגובה מתגלגלת חזרה, הגייטוויי המקורי כבר המשיך הלאה.

504 לעומת הקרובים שלו

504 Gateway Timeout: השרת במעלה הזרם כנראה תקין — רק איטי. הגייטוויי ויתר.

502 Bad Gateway: השרת במעלה הזרם החזיר משהו לא תקין או קרס באמצע תגובה. משהו באמת התקלקל.

503 Service Unavailable: השרת במעלה הזרם אמר במפורש "לא עכשיו." זה היה מכוון — אולי הוא עמוס, אולי הוא בתחזוקה.

408 Request Timeout: כיוון שונה לחלוטין. השרת נמאס לו לחכות ללקוח שיסיים לשלוח את הבקשה שלו.

ההבחנה המרכזית: 504 עוסקת בסבלנות הגייטוויי כלפי השרת במעלה הזרם. השרת במעלה הזרם עשוי להיות בריא לחלוטין.

הגדרת פסקי זמן

פסקי זמן צריכים לשקף מציאות, לא תקווה.

server {
    # נקודות קצה מהירות מקבלות פסקי זמן קצרים
    location /api/users {
        proxy_pass http://backend;
        proxy_read_timeout 30s;
    }

    # יצירת דוחות לוקחת זמן לגיטימי
    location /api/reports {
        proxy_pass http://backend;
        proxy_read_timeout 180s;
    }

    # בדיקות תקינות צריכות להיות מיידיות
    location /health {
        proxy_pass http://backend;
        proxy_read_timeout 5s;
    }
}

המלכודת: הגדרת אותו פסק זמן לכל מקום. לבדיקת תקינות ולמחולל דוחות אין שום דבר במשותף. יש להתייחס אליהם בצורה שונה.

בעיית שרשרת פסקי הזמן

כאן מערכות מבוזרות נעשות מעניינות. אולי יש לך:

  • פסק זמן לקוח: 120 שניות
  • פסק זמן גייטוויי: 60 שניות
  • פעולת Backend: 90 שניות

ה-Backend יסיים את עבודתו. אבל הגייטוויי פג בשישים שניות. הלקוח מקבל 504. ה-Backend מסיים 30 שניות מאוחר יותר, התגובה מוכנה, אבל החיבור כבר אינו קיים.

כל שכבה צריכה להכיר את השכבות שמתחתיה. פסקי הזמן של הגייטוויי צריכים לחרוג מזמן הפעולה הלגיטימי הארוך ביותר של שרתי ה-Backend שמנתבים אליהם בקשות.

הפתרון האמיתי: אל תגרום להם לחכות

הגדלת פסקי הזמן מטפלת בסימפטום. המחלה היא המתנה סינכרונית לפעולות איטיות.

לפעולות שלגיטימי שייקח להן זמן:

// אל תגרום ללקוח לחכות
app.post('/api/reports', async function(request, response) {
    const jobId = await queue.add('generate-report', request.body);
    
    // החזר מיד עם מזהה משרה
    response.status(202).json({
        jobId: jobId,
        statusUrl: `/api/jobs/${jobId}`
    });
});

// תן להם לבדוק את ההשלמה
app.get('/api/jobs/:id', async function(request, response) {
    const job = await queue.getJob(request.params.id);
    
    response.json({
        status: job.finished ? 'completed' : 'processing',
        progress: job.progress,
        result: job.finished ? job.result : null
    });
});

תבנית 202 Accepted. "שמעתי אותך. אני עובד על זה. חזור מאוחר יותר." שום פסק זמן לא יכול לפגוע בך כשאתה מגיב מיד.

לפעולות שצריכות להיות מהירות אבל אינן:

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

// עקוב אחר מה שבאמת איטי
app.use(function(request, response, next) {
    const start = Date.now();
    
    response.on('finish', function() {
        const duration = Date.now() - start;
        
        if(duration > 10000) {
            logger.warn('Slow request', {
                url: request.url,
                duration: duration
            });
        }
    });
    
    next();
});

התמודדות אלגנטית עם כשלים

כשהשרת במעלה הזרם איטי, יש לך אפשרויות מעבר ל"שגיאה" ו"המתן לנצח."

app.get('/api/data', async function(request, response) {
    try {
        const data = await Promise.race([
            fetchFreshData(),
            new Promise(function(_, reject) {
                setTimeout(function() {
                    reject(new Error('timeout'));
                }, 5000);
            })
        ]);
        
        response.json(data);
    }
    catch(error) {
        // הנתונים הטריים איטיים מדי? החזר נתונים מהמטמון.
        const cached = cache.get('data');
        
        if(cached) {
            response.json({
                ...cached,
                stale: true
            });
        }
        else {
            response.status(504).json({
                error: 'Unable to fetch data in time'
            });
        }
    }
});

נתונים ישנים מהמטמון טובים בדרך כלל מאין נתונים כלל. המשתמש מקבל משהו בזמן שאתה מבין מדוע שליפות טריות איטיות.

אסטרטגיית ניסיונות חוזרים מצד הלקוח

שגיאות 504 הן לעתים קרובות חולפות. השרת במעלה הזרם אולי היה עמוס לרגע, או שהייתה תקלת רשת זמנית. ניסיון חוזר הגיוני — עם השהייה הולכת וגדלה.

async function fetchWithRetry(url, maxRetries = 3) {
    for(let attempt = 0; attempt < maxRetries; attempt++) {
        const response = await fetch(url);
        
        if(response.status === 504 && attempt < maxRetries - 1) {
            // המתן יותר בין כל ניסיון
            await new Promise(function(resolve) {
                setTimeout(resolve, Math.pow(2, attempt) * 1000);
            });
            continue;
        }
        
        return response;
    }
}

השהייה המעריכית: שנייה אחת, אחר כך שתיים, אחר כך ארבע. תן למערכת זמן להתאושש.

מנתקי מעגל

אם שרת במעלה הזרם ממשיך לחרוג מפסק הזמן, הפסק לפנות אליו. מנתק מעגל מונע ממך להעמיס בקשות נוספות על מערכת שכבר מתקשה.

const CircuitBreaker = require('opossum');

const breaker = new CircuitBreaker(fetchFromBackend, {
    timeout: 30000,
    errorThresholdPercentage: 50,
    resetTimeout: 60000
});

breaker.fallback(function() {
    return { error: 'Service temporarily unavailable', cached: getCachedData() };
});

כשהכשלים עולים על 50%, המעגל נפתח. בקשות מקבלות מיד את תגובת ה-fallback במקום לחכות לפסק הזמן. לאחר 60 שניות, הוא שולח בקשת בדיקה אחת כדי לבחון אם השרת התאושש.

הדפוס העמוק יותר

שגיאות 504 חושפות משהו יסודי על מערכות מבוזרות: הן מוחזקות יחד על ידי סבלנות והנחות. כל רכיב מניח שהאחרים יגיבו "מהר מספיק." כשההנחות האלה נשברות — כשהגדרת "מהר מספיק" של מערכת אחת לא תואמת את השנייה — מתקבלות שגיאות גם כשלא שבור בפועל שום דבר.

הפתרון אינו רק הגדרה. זה תכנון מערכות שבהן המתנות ארוכות לא מתפשטות הלאה, שבהן נתוני מטמון ישנים מקובלים, שבהן "עדיין עובד על זה" היא תשובה לגיטימית. זה הכרה בכך שבמערכת מבוזרת, זמן הוא משאב שיכול לאזול.

שאלות נפוצות על 504 Gateway Timeout

האם 504 אומר שהשרת קרס?

לא. 504 אומר ספציפית שהשרת במעלה הזרם לא הגיב בזמן — הוא עשוי עדיין לרוץ, אפילו לסיים את הבקשה שלך ממש עכשיו. הגייטוויי פשוט הפסיק לחכות. זה שונה מ-502, שמצביע על כך שהשרת במעלה הזרם החזיר בפועל משהו לא תקין או נכשל.

האם עלי פשוט להגדיל את כל פסקי הזמן שלי כדי למנוע 504?

הגדלת פסקי הזמן מסתירה בעיות. אם נקודת קצה אמורה להגיב תוך 2 שניות אבל לפעמים לוקחת 90, יש לך באג ביצועים. תקן את זה במקום. שמור פסקי זמן ארוכים לפעולות שממש צריכות זמן, כמו יצירת דוחות או עיבוד אצווה.

למה אני מקבל 504 לסירוגין למרות שאותה בקשה בדרך כלל עובדת?

504 לסירוגין מצביעים בדרך כלל על תחרות על משאבים: מאגרי חיבורים למסד נתונים שמתמלאים, הפסקות של פינוי זיכרון, עומס רשת, או שירותי upstream שמואטים לפעמים תחת עומס. המערכת מתמודדת בדרך כלל, אבל לפעמים לא. בדוק מה שונה בזמן הכשלים — לעתים קרובות זה עניין של תזמון או עומס.

האם בטוח ללקוחות לנסות שוב לאחר 504?

בדרך כלל כן, עם הסתייגויות. לבקשות GET (קריאות), נסה שוב בחופשיות עם השהייה. לבקשות POST/PUT/DELETE (כתיבות), נסה שוב רק אם הפעולה היא אידמפוטנטית — אחרת אתה מסתכן בביצוע פעולות כפולות. אם אינך בטוח אם הבקשה המקורית הושלמה, בדוק לפני שתנסה שוב.

האם דף זה היה מועיל?

😔
🤨
😃