diff --git a/superset-frontend/packages/superset-ui-core/src/connection/constants.ts b/superset-frontend/packages/superset-ui-core/src/connection/constants.ts index 2dbdeca1777..c6d0e8b9914 100644 --- a/superset-frontend/packages/superset-ui-core/src/connection/constants.ts +++ b/superset-frontend/packages/superset-ui-core/src/connection/constants.ts @@ -32,7 +32,7 @@ export const CACHE_KEY = '@SUPERSET-UI/CONNECTION'; export const DEFAULT_FETCH_RETRY_OPTIONS: FetchRetryOptions = { retries: 3, retryDelay: 1000, - retryOn: [503], + retryOn: [502, 503, 504], }; export const COMMON_ERR_MESSAGES = { diff --git a/superset-frontend/src/components/Chart/chartAction.js b/superset-frontend/src/components/Chart/chartAction.js index 575402b525f..10e45b385a3 100644 --- a/superset-frontend/src/components/Chart/chartAction.js +++ b/superset-frontend/src/components/Chart/chartAction.js @@ -537,7 +537,11 @@ export function postChartFormData( export function redirectSQLLab(formData, history) { return dispatch => { - getChartDataRequest({ formData, resultFormat: 'json', resultType: 'query' }) + getChartDataRequest({ + formData, + resultFormat: 'json', + resultType: 'query', + }) .then(({ json }) => { const redirectUrl = '/sqllab/'; const payload = { diff --git a/superset-frontend/src/setup/setupClient.ts b/superset-frontend/src/setup/setupClient.ts index c6f2399436b..d5467bafd86 100644 --- a/superset-frontend/src/setup/setupClient.ts +++ b/superset-frontend/src/setup/setupClient.ts @@ -31,12 +31,44 @@ function getDefaultConfiguration(): ClientConfig { bootstrapData.common.conf.JWT_ACCESS_CSRF_COOKIE_NAME; const cookieCSRFToken = parseCookie()[jwtAccessCsrfCookieName] || ''; + // Configure retry behavior from backend settings + const retryConfig = bootstrapData.common.conf; + + // Create exponential backoff delay function with jitter + const createRetryDelayFunction = () => { + const baseDelay = retryConfig.SUPERSET_CLIENT_RETRY_DELAY || 1000; + const multiplier = + retryConfig.SUPERSET_CLIENT_RETRY_BACKOFF_MULTIPLIER || 2; + const maxDelay = retryConfig.SUPERSET_CLIENT_RETRY_MAX_DELAY || 10000; + + return (attempt: number) => { + // Calculate exponential backoff: baseDelay * Math.pow(multiplier, attempt) + const safeAttempt = Math.min(attempt, 10); // Limit attempt to prevent overflow + const exponentialDelay = baseDelay * Math.pow(multiplier, safeAttempt); + + // Apply max delay cap + const cappedDelay = Math.min(exponentialDelay, maxDelay); + + // Add random jitter to prevent thundering herd + const jitter = Math.random() * cappedDelay; + + return cappedDelay + jitter; + }; + }; + + const fetchRetryOptions = { + retries: retryConfig.SUPERSET_CLIENT_RETRY_ATTEMPTS || 3, + retryDelay: createRetryDelayFunction(), + retryOn: retryConfig.SUPERSET_CLIENT_RETRY_STATUS_CODES || [502, 503, 504], + }; + return { protocol: ['http:', 'https:'].includes(window?.location?.protocol) ? (window?.location?.protocol as 'http:' | 'https:') : undefined, host: window.location?.host || '', csrfToken: csrfToken || cookieCSRFToken, + fetchRetryOptions, }; } diff --git a/superset/config.py b/superset/config.py index 8d087fe3be4..b0a1e3b7f32 100644 --- a/superset/config.py +++ b/superset/config.py @@ -166,6 +166,17 @@ SAMPLES_ROW_LIMIT = 1000 NATIVE_FILTER_DEFAULT_ROW_LIMIT = 1000 # max rows retrieved by filter select auto complete FILTER_SELECT_ROW_LIMIT = 10000 + +# SupersetClient HTTP retry configuration +# Controls retry behavior for all HTTP requests made through SupersetClient +# This helps handle transient server errors (like 502 Bad Gateway) automatically +SUPERSET_CLIENT_RETRY_ATTEMPTS = 3 # Maximum number of retry attempts +SUPERSET_CLIENT_RETRY_DELAY = 1000 # Initial retry delay in milliseconds +SUPERSET_CLIENT_RETRY_BACKOFF_MULTIPLIER = 2 # Exponential backoff multiplier +SUPERSET_CLIENT_RETRY_MAX_DELAY = 10000 # Maximum retry delay cap in milliseconds +SUPERSET_CLIENT_RETRY_JITTER_MAX = 1000 # Maximum random jitter in milliseconds +# HTTP status codes that should trigger retries (502, 503, 504 gateway errors) +SUPERSET_CLIENT_RETRY_STATUS_CODES = [502, 503, 504] # default time filter in explore # values may be "Last day", "Last week", " : now", etc. DEFAULT_TIME_FILTER = NO_TIME_RANGE diff --git a/superset/views/base.py b/superset/views/base.py index 10f3cf3726e..6192da03950 100644 --- a/superset/views/base.py +++ b/superset/views/base.py @@ -112,6 +112,12 @@ FRONTEND_CONF_KEYS = ( "ALERT_REPORTS_DEFAULT_RETENTION", "ALERT_REPORTS_DEFAULT_WORKING_TIMEOUT", "NATIVE_FILTER_DEFAULT_ROW_LIMIT", + "SUPERSET_CLIENT_RETRY_ATTEMPTS", + "SUPERSET_CLIENT_RETRY_DELAY", + "SUPERSET_CLIENT_RETRY_BACKOFF_MULTIPLIER", + "SUPERSET_CLIENT_RETRY_MAX_DELAY", + "SUPERSET_CLIENT_RETRY_JITTER_MAX", + "SUPERSET_CLIENT_RETRY_STATUS_CODES", "PREVENT_UNSAFE_DEFAULT_URLS_ON_DATASET", "JWT_ACCESS_CSRF_COOKIE_NAME", "SQLLAB_QUERY_RESULT_TIMEOUT",