Understanding dynamic table target lag

Dynamic table refresh is triggered by the data’s target lag, which determines how outdated it can be. You can set a fixed target lag or set the dynamic table to DOWNSTREAM, making its refresh timing depend on the dynamic tables that depend on it.

The target lag for a dynamic table is measured relative to the dynamic tables at the root of the graph, not the dynamic tables directly upstream. To see the graph of tables connected to your dynamic table, see View the graph of tables connected to your dynamic tables.

Snowflake schedules refreshes to keep the actual lag of your dynamic tables below their target lag. The duration of each refresh depends on the query, data pattern, and warehouse size. When choosing a target lag, consider the time needed to refresh each dynamic table in a chain to the root. If you don’t, some refreshes might be skipped, leading to a higher actual lag.

Note

For dynamic tables with the SCHEDULER attribute explicitly set to DISABLE, target lag doesn’t apply and automatic refreshes are disabled. These dynamic tables can only be refreshed manually. For more information, see Manual refresh of dynamic tables with the scheduler disabled.

Types of target lag

You specify target lag in one of the following ways. Target lag is inversely proportional to the dynamic table’s refresh frequency: frequent refreshes imply a lower lag.

  1. Measure of freshness: Defines the maximum amount of time that the dynamic table’s content should lag behind updates to the base tables. The following example sets my_dynamic_table to refresh and maintain freshness within every hour:

    ALTER DYNAMIC TABLE my_dynamic_table SET TARGET_LAG = '1 hour';
  2. Downstream: Specifies that the dynamic table should refresh on demand when downstream tables (tables that depend on this table) refresh. This refresh can be triggered by initialization at creation, manual refresh, or scheduled refresh of a downstream table.

    When refresh_mode is set to downstream, the refresh schedule of a dynamic table is driven by the most demanding (shortest) lag of its downstream dependents. For example, if one downstream dependent table requires data that is no older than 10 minutes and another downstream dependent table requires data that is no older than 1 hour, the refresh schedule of this dynamic table will be every 10 minutes because that is the shortest lag of its downstream dependents.

    In the following example, my_dynamic_table is set to refresh based on the target lag of its downstream dynamic tables. If my_dynamic_table doesn’t have any dynamic tables that depend on it, then it won’t refresh.

    ALTER DYNAMIC TABLE my_dynamic_table SET TARGET_LAG = DOWNSTREAM;

    For more examples of downstream target lag, see Example: Target lag for dynamic table chains.

How Snowflake schedules refreshes

Snowflake schedules refreshes slightly earlier than the target lag to allow time for the refresh to complete. For example, if you set the target lag to 5 minutes, the table might refresh more frequently than every five minutes. Actual refresh intervals are often shorter than the specified lag.

Note

Target lag is a target, not a guarantee. Snowflake attempts to keep data within the target lag, but actual lag may exceed the target because of factors such as warehouse size, data volume, and query complexity.

For guidance on adjusting target lag for your workload, see Alter the warehouse or target lag for dynamic tables. For information about optimizing your target lag, see Identify the right target lag.

How upstream and downstream relationships affect target lag

The following diagram illustrates suspend, resume, and manual refresh operations in the context of upstream and downstream relationships to other dynamic tables.

Relationship between dynamic tables. Used to help explain suspend, resume, and manual refresh.

The diagram depicts a simple declarative data pipeline built with dynamic tables:

  • DT2 is described as downstream of DT1 because it depends on that dynamic table, and as upstream of DT3, which depends on it.
  • DT3 is downstream of both DT2 and DT1 because it depends on DT2 directly and on DT1 indirectly.
  • DT1 is directly or indirectly upstream of the other dynamic tables.

Example: Target lag for dynamic table chains

Consider the following example where a dynamic table (DT2) reads from another dynamic table (DT1) to materialize its contents. In this scenario, a report consumes DT2’s data via a query.

Simple example of two dynamic tables: DT2, which is defined based on DT1.

The following results are possible, depending on how each dynamic table specifies its lag:

DT1DT2Refresh results
TARGET_LAG = DOWNSTREAMTARGET_LAG = 10minutesDT2 is updated at least every 10 minutes. DT1 infers its lag from DT2 and is updated every time DT2 requires updates.
TARGET_LAG = 10minutesTARGET_LAG = DOWNSTREAMThis scenario should be avoided. The report query will not receive any data. DT1 is frequently refreshed and DT2 is not refreshed because there’s no dynamic table that’s based on DT2.
TARGET_LAG = 5minutesTARGET_LAG = 10minutesDT2 is updated approximately every 10 minutes with data from DT1 that’s at most 5 minutes old.
TARGET_LAG = DOWNSTREAMTARGET_LAG = DOWNSTREAMNeither DT1 nor DT2 is refreshed periodically because both of them have a downstream lag, and neither has a downstream consumer with a defined lag.

Manually refresh dynamic tables

Manual refresh of dynamic tables with the scheduler enabled

You can manually refresh a dynamic table to include the latest data without waiting for the next scheduled refresh. This is useful for one-time updates or when a table has a large target lag and the next refresh occurs much later.

Tip

Avoid frequent manual refreshes on dynamic tables with downstream dynamic tables that are expected to refresh according to target lag. These kinds of manual refreshes can cause scheduled refreshes to skip and prevent downstream tables from updating.

To manually refresh, use the ALTER DYNAMIC TABLE … REFRESH command or Snowsight as shown in the following steps:

ALTER DYNAMIC TABLE my_dynamic_table REFRESH

For situations that require precise refresh timing, such as aligning refreshes with external system schedules or batch processing windows, you can use a task with a CRON expression to trigger the refresh.

For example:

-- Create the task
CREATE TASK my_dt_refresh_task
  WAREHOUSE = my_wh
  SCHEDULE = 'USING CRON 0 0 * * * America/Los_Angeles' -- Example: daily at midnight PST
  COMMENT = 'Daily 5pm PT manual refresh of my_dynamic_table'
  AS
    ALTER DYNAMIC TABLE my_dynamic_table REFRESH;

-- Enable the task
ALTER TASK my_dt_refresh_task RESUME;

-- Show the task
SHOW TASKS LIKE 'my_dt_refresh_task';
+------------+-----------------+-------------------------------------+---------------+-------------+--------------+-------------------------------------------------+-----------|-------------------------------------------+------------------+---------+----------------------------------------------+-----------+-----------------------------+-------------------+-------------------------------|-------------------+-----------------+--------+---------------------+-----------------------+---------------------+-----------------+----------------------------+-----------------+
| CREATED_ON | NAME            | ID                                  | DATABASE_NAME | SCHEMA_NAME | OWNER        | COMMENT                                         | WAREHOUSE | SCHEDULE                                  | [ ] PREDECESSORS | STATE   | DEFINITION                                   | CONDITION | ALLOW_OVERLAPPING_EXECUTION | ERROR_INTEGRATION | LAST_COMMITTED_ON             | LAST_SUSPENDED_ON | OWNER_ROLE_TYPE | CONFIG | TASK_RELATIONS      | LAST_SUSPENDED_REASON | SUCCESS_INTEGRATION | SCHEDULING_MODE | TARGET_COMPLETION_INTERVAL | EXECUTE_AS_USER |
|------------+-----------------+-------------------------------------+---------------+-------------+--------------+-------------------------------------------------+-----------+-------------------------------------------+------------------+---------+----------------------------------------------+-----------+-----------------------------+-------------------+-------------------------------+-------------------+-----------------+--------+---------------------+-----------------------+---------------------+-----------------+----------------------------+-----------------|
| 2025-10-02 | DT_REFRESH_TASK | 01bf6f0d-690f-f373-0000-000000025e3d| mydb          | my_schema   | ACCOUNTADMIN | Daily 5pm PT manual refresh of my_dynamic_table | mywh      | USING CRON 0 17 * * * America/Los_Angeles | []               | Started | ALTER DYNAMIC TABLE my_dynamic_table REFRESH | null      | false                       | null              | 2025-10-02 05:08:52.897 +0000 | null              | ROLE            | null   | {"Predecessors":[]} | null                  | null                | null            | null                       | null            |
+------------+-----------------+-------------------------------------+---------------+-------------+--------------+-------------------------------------------------+-----------|-------------------------------------------+------------------+---------+----------------------------------------------+-----------+-----------------------------+-------------------+-------------------------------|-------------------+-----------------+--------+---------------------+-----------------------+---------------------+-----------------+----------------------------+-----------------+

For most cases, Snowflake recommends using target lag, which optimizes refresh frequency and can reduce costs compared to fixed CRON schedules that might run unnecessarily.

Manual refresh of dynamic tables with the scheduler disabled

Dynamic tables with the SCHEDULER attribute set to DISABLE can only be refreshed manually.

This type of manual refresh refreshes only that dynamic table. It doesn’t cascade to any upstream dynamic tables, regardless of their scheduler state.

In the DYNAMIC_TABLE_REFRESH_HISTORY output, the REFRESH_TRIGGER value for these refreshes is MANUAL. No SCHEDULED entries are generated for dynamic tables with SCHEDULER set to DISABLE.

This behavior allows external orchestrators, such as dbt, to issue one manual refresh per dynamic table without triggering upstream refreshes.

To disable the scheduler and then manually refresh a dynamic table, use the ALTER DYNAMIC TABLE command as shown in the following steps:

  1. Disable the scheduler:

    ALTER DYNAMIC TABLE my_dynamic_table SET SCHEDULER = DISABLE
  2. Manually refresh the dynamic table:

    ALTER DYNAMIC TABLE my_dynamic_table REFRESH