---
title: "Refresh a computed column"
sidebarTitle: "Refresh Computed Column"
description: "Populate a computed column and update it when its inputs change."
icon: arrows-rotate
keywords: ["refresh_column", "backfill", "job", "idempotent", "REFRESH COLUMN"]
availability:
embedded: available
enterprise: available
---
Refresh a [computed column](/feature-engineering/add-computed-column) to fill
new rows and recompute values whose inputs changed. The example below continues
with the `doubled` column from the computed-column guide.
In the 0.41 SDK release, declaration and refresh in LanceDB Embedded require a source build
with the `experimental-functions` Cargo feature. Published Python and TypeScript
packages leave it disabled. LanceDB Enterprise operations do not require
this feature; existing computed-column data remains readable in default builds.
```python Python icon="python"
table.refresh_column("doubled")
# RefreshColumnResult(rows_filled=2, version=4)
table.to_arrow().sort_by("x").to_pandas()
# x doubled
# 0 1 2
# 1 2 4
```
```sql SQL
REFRESH COLUMN doubled ON my_table
```
The refresh commits ordinary table versions (the values, then a
metadata-only record of the inputs they were computed from), so
[versioning](/management/table#versioning) applies to it like any other write.
The SQL form is [`REFRESH COLUMN`](/sql/refresh#refresh-column), with the same resource options.
## What a refresh recomputes {#idempotent}
A refresh fills rows that have no value and recomputes rows whose inputs
changed since they were computed. Every other row is left alone, so a second
refresh with nothing changed does nothing. This supports two common workflows.
**Appending is cheap.** Rows added since the last refresh are filled by the
next one; filled rows whose inputs are unchanged are skipped. Backfilling a
column and then ingesting more data does not mean recomputing the backfill.
**A filled value catches up with its input at the next refresh.** If `doubled`
derives from `x` and a row's `x` is later updated, `doubled` keeps its old value
until the next refresh recomputes it.
That is what makes the call idempotent and safe to retry. However, unlike a
spreadsheet formula, nothing is recomputed until you refresh, so a derived
column is stale between an update and the next refresh.
## Automatic refresh {#automatic}
Set scheduling options when declaring a computed column:
```python
table.add_columns(
computed={"doubled": "x * 2"},
auto_refresh_enabled=True,
auto_refresh_interval_seconds=300,
)
```
These options apply to every computed output declared in that call. Automatic
refresh is enabled by default. When the interval is omitted, the server
determines when to refresh. Local tables store the setting, but automatic
execution requires a Cloud or Enterprise server with its separate
`column_refresh.enabled` switch turned on. That server switch is disabled in
the base configuration.
The interval is a positive minimum delay after the last successful refresh
of that output. A first refresh is immediately eligible. The scheduler still
checks input freshness and waits for computed ancestors to settle; the timer
does not make unchanged inputs require recomputation. Each declared output's
interval is independent, so refreshing a sibling does not postpone it.
Pacing uses retained live Job Registry history. If completed jobs are archived
before a configured interval expires, the scheduler can lose the last-success
timestamp and refresh earlier. Leave archival disabled, or retain live history
for at least the longest configured column interval. Archival is disabled in
the base configuration; its default retention threshold is seven days.
Change a stored setting through SQL:
```sql
ALTER TABLE my_table ALTER COLUMN doubled SET auto_refresh_enabled = false;
ALTER TABLE my_table ALTER COLUMN doubled SET auto_refresh_interval_seconds = 600;
ALTER TABLE my_table ALTER COLUMN doubled SET auto_refresh_enabled = true;
```
The current setting is in the column's own field metadata,
`computed_column.auto_refresh_enabled`, readable from `table.schema` without
submitting a job to ask.
Setting the flag is a metadata-only change and always succeeds, independent of
whether the scheduler can actually refresh the column -- a deployment issue
that keeps the background job from running (for example, a worker that cannot
reach object storage) does not stop the `ALTER` from committing. Check the
column's job history, not just its metadata, if values stop appearing after
re-enabling.
**Disabling does not skip or drop writes.** Rows written while disabled
commit with no computed value, the same as a row written before the column
existed; they do not queue anywhere separate. Inflight jobs may finish, and
manual refresh remains available throughout.
**Re-enabling catches up the entire backlog in one pass**, not just rows
written after that point: the next scheduler tick processes every row left
unfilled since the column was disabled, in a single refresh, with no
further write required to trigger it. This is "catch up," not "resume
cadence" -- a column disabled for an hour and re-enabled fills everything
that accumulated during that hour, immediately, rather than only picking up
new rows from the moment it is re-enabled.
## Refreshing part of a table {#partial}
Enterprise In SQL a refresh can be scoped to a
predicate, which is useful for filling a slice first and checking it before
committing to the whole table:
```sql
REFRESH COLUMN embedding ON documents WHERE created_at > '2026-01-01'
```
**Enterprise job type:** `computed_refresh` for a SQL-expression column.
{/* Draft for the Functions release: `function_refresh` for one filled by a registered [function](/management/function). */}
## Run a refresh on Enterprise {#async}
Enterprise
With an Enterprise cluster that enables computed columns, the refresh runs on the
cluster. `refresh_column` raises
there; use `refresh_column_async`, which returns a [job](/basics/jobs) you can
wait on or poll instead of holding a connection open:
```python
job = table.refresh_column_async("doubled")
result = job.wait()
result.rows_assigned
# 2
job.status()
# 'finished'
```
Poll with `status()`, resolve with `wait()`. `status()` is a point snapshot and
does not raise, which is what makes it safe in a loop; `wait()` raises
`JobFailedError`, `JobCancelledError` or `TimeoutError`.
A long backfill can therefore be started, left, and checked on from a different
process.
### Configure workers and resources {#resources}
SQL exposes the worker budget directly, which is the lever when a backfill is
too slow or too expensive:
```sql
REFRESH COLUMN embedding ON documents
WITH (cpu_millis = 8000, memory_bytes = 17179869184, gpu_count = 2)
```
`num_workers` sets the count instead of a per-worker budget; the two are
alternatives rather than a pair. `batch_size` controls how many rows a worker
takes at a time, and `error_policy = 'skip'` continues past rows that fail
rather than failing the job.
## When a refresh fails {#failures}
What a failure keeps depends on where the refresh ran. `refresh_column` in your
process writes the column in one commit, so a failure leaves the column as it
was. A refresh job in LanceDB Enterprise commits finished batches as it goes, so some
values can remain after a failure.
Either way, recover by fixing the cause and refreshing again. The new refresh
fills rows that are still empty or whose inputs changed, and normally skips
values that were kept. That is not guaranteed: a refresh can recompute values
that were already written (for example when the record of the inputs they
were computed from was not saved), so do not count on a retry costing only the
rows that did not complete.
{/*
Draft for the Functions release.
For a column filled by a [function](/management/function), the usual cause is
the worker environment rather than the data. See
[environment mismatches](/management/function#mismatch-symptoms) for the
symptoms, which rarely name their cause.
*/}
## Related guides {#next}
Waiting on, inspecting, and cancelling server-side work.
The same idea, for a whole derived table.