extracts
Why Tableau extract refreshes fail — and how to find the ones that keep failing
Karan Arora · Founder · 9 min read · Last reviewed:
Every Tableau Server carries some extract refresh failures. A 2–4% failure rate across a quarter looks alarming in a status report and means almost nothing by itself. What matters is the shape of the failures: whether they are spread thinly across the estate — transient, self-healing, ignorable — or concentrated in a handful of workbooks that fail again and again while their owners quietly stop trusting the data.
This guide covers where refresh failures are actually recorded, the five ways refreshes fail in practice, and the SQL that separates repeat offenders from background noise.
Where failures are recorded
On Tableau Server, every background job — extract refreshes included — is
written to the background_jobs table in the repository, the PostgreSQL
database (workgroup) that Tableau Server maintains about itself. The
columns that matter here:
| Column | What it holds |
| --- | --- |
| job_name | The job type — extract refreshes are Refresh Extracts and Increment Extracts |
| title | The workbook or data source being refreshed |
| finish_code | 0 = success, 1 = error, 2 = canceled |
| notes | The error message, when there is one |
| created_at / started_at / completed_at | Queued, started, finished — the gaps between them are the queue wait and the runtime |
The three timestamps deserve a moment, because failure analysis goes wrong
without them. created_at is when the job was queued; started_at is
when a backgrounder picked it up; completed_at is when it finished,
however it finished. started_at - created_at is queue wait,
completed_at - started_at is runtime — and they fail differently. A job
canceled after two hours of runtime is a timeout; a job that sat in the
queue for two hours and then got canceled is a scheduling problem wearing
a timeout's clothes. Collapse the two and you will fix the wrong thing —
reading the queue, not the job
covers the queue half in depth.
Two practical notes. First, read the repository through the readonly
user, which your administrator enables — never the tblwgadmin superuser.
Querying the repository safely
covers the access setup and session discipline in full, and
our methodology shows how we apply it.
Second, the repository keeps a bounded window of job history, and the
default is shorter than people expect. Before assuming you have 90 days to
analyze, check how far back your background_jobs rows actually go —
SELECT MIN(created_at) FROM background_jobs answers it in one line.
The five ways refreshes fail
Nearly every refresh failure we see in reviews falls into one of five modes, and the fix is different for each.
1. Credentials and tokens
The most common chronic failure. The extract was published with embedded credentials that have since expired, been rotated by IT policy, or belong to someone who left. OAuth tokens age out; service-account passwords get rotated without anyone updating the data source. The refresh then fails identically on every run — these are the workbooks that fail dozens of times in a quarter.
2. The source changed shape
A column the extract depends on was renamed, retyped, or dropped upstream.
The failure starts on a specific date — the deployment date of the
upstream change — and repeats until someone fixes the workbook or the
source. The start date is the diagnostic clue, and it is sitting in
created_at.
Incremental refreshes have their own version of this mode. An incremental
refresh only appends rows that are new since the last run, keyed on a
column the author chose; it cannot see updates or deletes to rows it
already holds, and it breaks in confusing ways when the key column's
assumptions change. An Increment Extracts job that starts failing — or
worse, starts silently drifting from the source — after an upstream
change is a case for re-examining the increment key, and often for
scheduling an occasional full refresh alongside the incremental ones.
3. Timeouts
Long-running refreshes are canceled when they hit the backgrounder's
query limit (backgrounder.querylimit, configured in TSM — 7,200 seconds
by default). One documented caveat that explains a symptom admins actually
observe: a job that hits the limit is not killed instantly — it can keep
running for several more minutes while the cancellation completes. A
refresh showing 2h06m of runtime did not beat the limit; it was being
stopped.
Timeouts deserve suspicion rather than a bigger limit: a refresh that used to finish and now times out usually means the extract has grown, the source has slowed, or a full refresh is running where an incremental one should. Raising the limit treats the symptom and lengthens the queue for everyone else.
4. Resource pressure on the backgrounder
Refreshes that fail intermittently, at busy times, with varying error messages — out-of-memory kills, lost connections, jobs dying mid-run. No single workbook is at fault; the backgrounder pool is oversubscribed at peak. The failures cluster by time of day rather than by title. If your delays also cluster — refreshes queuing behind each other every morning — the schedule, not the content, is the thing to fix. Whether the pool itself is undersized is a measurable question, covered in how many backgrounder processes your Server should have.
5. Suspended tasks
Two distinct mechanisms suspend refreshes, and they are worth keeping apart, because they have different causes and different fixes — all they share is the ending: the refresh quietly stops running.
Failure-based suspension. The platform stops retrying a task that keeps failing. Tableau Cloud suspends a refresh task after five consecutive failures and notifies the owner; recent Tableau Server versions can do the same, depending on version and site settings. The part readers tend to assume wrongly: a suspended refresh does not resume by itself. Fixing the credential or schema problem is necessary but not sufficient — the schedule must be restarted, from the data source's Connection Details or the task's "Try again." A suspended task also stops generating failures, which means it disappears from naive failure counts while the data underneath goes stale. If a chronic offender suddenly "recovers" in your numbers, check whether it was actually suspended.
Inactivity-based suspension. Separately, refreshes can be auto-suspended because nobody is using the content: workbooks that sit unopened long enough have their scheduled refreshes paused, so backgrounder cycles stop being spent on data nobody reads. Nothing failed, so these never appear in a failure analysis at all. The fix is not a repair but a decision — retire the content, or justify why it should keep refreshing. This is where refresh analysis meets stale content: extracts suspended for inactivity are the platform volunteering the first names for your retirement list.
The SQL that finds repeat offenders
Failure percentage alone misleads: a workbook that failed once in two runs shows 50%, while the genuinely broken one shows 45 failures spread over 1,100 jobs. Count failures and rate together, and set a floor:
SET TRANSACTION READ ONLY;
SELECT
title,
COUNT(*) AS runs,
COUNT(*) FILTER (WHERE finish_code = 1) AS failures,
ROUND(100.0 * COUNT(*) FILTER (WHERE finish_code = 1)
/ COUNT(*), 1) AS failure_pct,
MAX(created_at)
FILTER (WHERE finish_code = 1) AS last_failure
FROM background_jobs
WHERE job_name IN ('Refresh Extracts', 'Increment Extracts')
AND created_at >= NOW() - INTERVAL '90 days'
GROUP BY title
HAVING COUNT(*) FILTER (WHERE finish_code = 1) >= 3
ORDER BY failures DESC, failure_pct DESC;The HAVING floor of three failures is deliberate. One or two failures in
a window is weather. Three or more is a pattern with a cause.
To separate chronic breakage from transient noise, look for streaks — consecutive failures with no success in between. This is a standard gaps-and-islands query:
WITH runs AS (
SELECT
title,
created_at,
finish_code,
ROW_NUMBER() OVER (PARTITION BY title ORDER BY created_at)
- ROW_NUMBER() OVER (
PARTITION BY title, finish_code ORDER BY created_at
) AS streak_group
FROM background_jobs
WHERE job_name IN ('Refresh Extracts', 'Increment Extracts')
AND created_at >= NOW() - INTERVAL '90 days'
)
SELECT
title,
COUNT(*) AS consecutive_failures,
MIN(created_at) AS streak_started,
MAX(created_at) AS streak_ended
FROM runs
WHERE finish_code = 1
GROUP BY title, streak_group
HAVING COUNT(*) >= 3
ORDER BY consecutive_failures DESC;A long streak that is still open is mode 1 or 2 — something structural, failing every time. Short streaks scattered across many titles at similar times of day point at mode 4. Streaks that end abruptly without a success may be mode 5: suspended, not fixed.
Finally, the error text itself. notes carries the message, and a crude
classification goes a long way:
SELECT
CASE
WHEN notes ILIKE '%password%'
OR notes ILIKE '%authenticat%' THEN 'credentials'
WHEN notes ILIKE '%timeout%'
OR notes ILIKE '%cancel%' THEN 'timeout or canceled'
WHEN notes ILIKE '%column%'
OR notes ILIKE '%field%' THEN 'schema change'
ELSE 'other'
END AS failure_class,
COUNT(*) AS failures
FROM background_jobs
WHERE job_name IN ('Refresh Extracts', 'Increment Extracts')
AND finish_code = 1
AND created_at >= NOW() - INTERVAL '90 days'
GROUP BY 1
ORDER BY 2 DESC;Error messages vary by connector and version, so treat the buckets as a
first cut and read the raw notes for anything that lands in other.
Reading the result
The numbers from these three queries answer the questions that matter:
- Concentration. If the top five titles account for most of the failures — the usual case — you have a short, fixable list, not a platform problem. In one illustrative pattern from our rule library, 45 failures out of 1,166 jobs traced back to just six workbooks.
- Mode. Long streaks with stable error text are credentials or schema changes: assign an owner per title. Time-clustered scatter is capacity: fix the schedule or the backgrounder count, not the workbooks.
- Staleness risk. For each chronic offender, the delta between
last_failureand the last success is how old that data actually is. That number is usually the one that gets a fix prioritized.
This repeat-offender query is one rule from the library behind our full deployment audit, which runs every failure rule across the estate and hands back the prioritized list.
What to do about each mode
The point of classifying is that each mode has a different owner and a different fix. Broad-brush responses — restart the backgrounders, raise the timeout, re-run everything — treat all five modes as one and fix none of them.
Credentials. Re-embedding a password is the fix for today; the fix that lasts is structural. Refreshes that matter should run on service accounts with a documented rotation path, not on the publisher's personal login, and OAuth-based connections need someone who knows the token lifetime. Every chronic credential failure has an implicit question attached: who owns this data source now?
Schema changes. Correlate the streak's start date with your upstream release calendar and the culprit deployment is usually obvious. The lasting fix is procedural — the team that owns the warehouse table should know which extracts depend on it before renaming a column, which is an argument for keeping the dependency list somewhere queryable.
Timeouts. Before touching backgrounder.querylimit, look at the
growth curve: has this extract's runtime been rising for months? Filters,
aggregation to the level the dashboard actually uses, or a conversion
from full to incremental refresh usually buys back more than a larger
limit would. Raising the limit is occasionally right — but it is a
capacity decision, made with the queue in view, not a per-workbook patch.
Resource pressure. If failures cluster at peak hours, count what is scheduled to run concurrently in that window before buying hardware. Estates accumulate 8 a.m. schedules the way inboxes accumulate subscriptions; spreading the same jobs across the morning often removes the pressure entirely. The backgrounder count itself is a sizing question — measured, it becomes an arithmetic problem rather than a debate.
Suspended tasks. For failure-based suspensions: fix the underlying cause first, then restart the schedule — Connection Details or "Try again" — and confirm the next run succeeded. Resuming without a fix schedules a fresh streak of failures and another suspension. For inactivity-based suspensions, don't reflexively resume anything: treat the list as stale-content candidates and make the retire-or-justify call per item.
Tableau Cloud: same analysis, different source
Tableau Cloud has no repository to query. The equivalent evidence lives in the Admin Insights project — the Job Performance data source records background job outcomes with job type, status, and timing — and the same concentration-and-streak analysis applies, built in a workbook instead of SQL. The five failure modes are identical; only the query surface changes.
Fix the list, then watch it
The output of this analysis is a short list with owners: re-embed or rotate credentials here, repair a schema dependency there, convert one oversized full refresh to incremental, and re-balance a schedule window. None of it is glamorous. All of it is measurable — which means the same queries, run a month later, tell you whether the fixes held.