Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

runtime-next: add minimum interval for post-txn triggers - #3110

#3110
Merged
dgreer-dev merged 1 commit into
masterestuary/flow:masterfrom
daveg/3060-trigger-minimum-fire-intervalestuary/flow:daveg/3060-trigger-minimum-fire-intervalCopy head branch name to clipboard
Jul 6, 2026
Merged

runtime-next: add minimum interval for post-txn triggers#3110
dgreer-dev merged 1 commit into
masterestuary/flow:masterfrom
daveg/3060-trigger-minimum-fire-intervalestuary/flow:daveg/3060-trigger-minimum-fire-intervalCopy head branch name to clipboard

Conversation

@dgreer-dev

Copy link
Copy Markdown
Contributor

Description:

Adds an optional minimum fire interval (debounce) to materialization webhook triggers, for the V2 runtime. Fixes #3060.

Triggers previously fired once per committed transaction that materialized data, so a burst of transactions produced a burst of webhook deliveries (potentially costly). With an interval set, a burst collapses into at most one delivery per interval, whose payload covers the union of the collapsed transactions. The old connector-side dbt trigger had an equivalent debounce, this generalizes it to runtime triggers.

Mechanically: each trigger config accumulates a pending window in a leader-lifetime accumulator, keyed by the config's method+URL. At commit time the accumulator is persisted and configs whose interval has elapsed fire with their accumulated window. The rest keep accumulating and fire either at the first commit at-or-after their interval elapses (fast path) or, if the task goes quiet, from the leader's idle loop once the deadline passes (so a debounced window never hangs waiting for a next transaction).

Workflow steps:

Add interval to a trigger config on a materialization:

triggers:
  config:
    - url: "https://cloud.getdbt.com/api/v2/accounts/123/jobs/456/run/"
      headers:
        Authorization: "Token dbt-token"
      payloadTemplate: |
        {"cause": "Estuary trigger {{run_id}}"}
      interval: 30m

Unset preserves today's fire-every-transaction behavior; existing specs are unchanged. The field is per-config, so e.g. a Slack notification can fire every transaction while a dbt run on the same materialization is debounced to 30m.

Documentation links affected:

  • site/docs/concepts/materialization/materialization-triggers.md — updated in this PR: interval added to the spec example, properties table, and SOPS-excluded-fields list.

Notes for reviewers:

  • Two fire paths, one decision function. A due window normally fires with the transaction that made it due (commit path, no added latency). If the task goes quiet — frontiers only arrive when journal content advances — HeadIdle fires due windows from idle and otherwise sleeps until the earliest pending deadline, so a burst's tail is delivered within its interval even with no further commits. The idle fire requires Tail::Done and rotates the Tail into its normal TriggerPersistDone sequence, so it can't race a commit-path fire.
  • Persisted format change + compatibility. The durable "trigger-params" blob changes from a single TriggerVariables to a {method+url → TriggerVariables} map. Both runtimes decode through the new serde-untagged models::triggers::PersistedTriggerParams (the formats are mutually unambiguous), so: V2 recovering a pre-upgrade blob fans it out to all configs (its original fire-all semantics), and V1 after a V2→V1 rollback merges a map blob into one window (its normal fire-all behavior). Residual: a binary downgrade to a release before this PR has neither fallback — don't downgrade past this release while a trigger is mid-debounce.
  • Debounce state is in-memory (last_fire): a leader failover resets the window, worst case one early fire — same as pre-debounce behavior. Recovery re-fires the persisted accumulator verbatim (at-least-once), which can also mean one early fire post-recovery.
  • Duplicate configs (same method+URL) aren't supported since the key is the debounce identity: first config wins, later duplicates WARN at task startup and never fire. A pending window whose config was removed on republish is dropped with a WARN rather than failing the task (failing would crash-loop, since the config stays gone for the build's life).
  • V1 is otherwise untouched: it accepts but ignores interval (no debounce), and its only code change is the tolerant PersistedTriggerParams decode above.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🚀 Preview deployed to https://docs.estuary.dev/pr-preview/pr-3110/

📄 Changed pages:

@dgreer-dev
dgreer-dev force-pushed the daveg/3060-trigger-minimum-fire-interval branch from 8ceb6eb to 4b68712 Compare July 6, 2026 18:11
@dgreer-dev

Copy link
Copy Markdown
Contributor Author

Reworked per feedback, should be much simpler now:

  • interval is now a single top-level triggers, shared by all configs, rather than per-config. Debounce state collapses to one pending window + one last-fire clock.
  • format in rocksdb is the same for the v1/v2, so no complexity there

@williamhbaker williamhbaker left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@dgreer-dev
dgreer-dev merged commit fae09b2 into master Jul 6, 2026
12 of 13 checks passed
@dgreer-dev
dgreer-dev deleted the daveg/3060-trigger-minimum-fire-interval branch July 6, 2026 20:27
mdibaiee added a commit to estuary/homebrew-flowctl that referenced this pull request Jul 15, 2026
## What's Changed
* runtime: periodically store primary FSMHints of V2 shards by @williamhbaker in estuary/flow#3027
* mise build:flowctl for a local flowctl binary by @mdibaiee in estuary/flow#3031
* go.mod: bump gazette to latest by @williamhbaker in estuary/flow#3033
* data-plane-controller: dns and s3 passthroughs by @qaoj in estuary/flow#3022
* Self-service private-link configuration by @jshearer in estuary/flow#2944
* supabase: scoped refresh tokens so CI writes via PostgREST instead of direct psql by @skord in estuary/flow#3013
* docs: Describe SQL Server replica suport by @willdonnelly in estuary/flow#3040
* proto-gazette: regenerate to pick up SUSPEND_KEEP by @williamhbaker in estuary/flow#3041
* docs: correct custom-column-types backfill behavior for DDL changes by @jwhartley in estuary/flow#3036
* Refresh token GraphQL operations and token exchange endpoint by @GregorShear in estuary/flow#3020
* docs: shopify stream & resource config additions by @Alex-Bair in estuary/flow#3034
* validation/collection: exclude `flow://inferred-schema` from the managed-defs redact check by @jshearer in estuary/flow#3046
* docs: update Bigtable connector permissions example by @mwillman-estuary in estuary/flow#3045
* docs: disable auto-discover for multi-binding file source captures by @jwhartley in estuary/flow#3037
* docs: add streams and API pinning details to stripe-native by @nicolaslazo in estuary/flow#3055
* data-plane-controller: restart setting by @qaoj in estuary/flow#3048
* deploy: route control-plane services through the VPC NAT for a stable egress IP by @skord in estuary/flow#3044
* docs: document the Exclude Flow Document (no_flow_document) materialization option by @jwhartley in estuary/flow#3018
* oidc-discovery-server: survive direct VPC egress cold-start on deploy by @skord in estuary/flow#3062
* docs: clarify retain_existing_data_on_backfill requires allow_existing_tables_for_new_bindings by @jwhartley in estuary/flow#3049
* data-plane-controller: restart on ansiblehost by @qaoj in estuary/flow#3066
* flow-web: Bump version to release by @jshearer in estuary/flow#3068
* ops-catalog: misc updates by @williamhbaker in estuary/flow#3067
* proto-flow: tolerate unknown JSON fields in connector protocols by @williamhbaker in estuary/flow#3059
* flowctl: add raw split-shards to scale out V2 tasks by @williamhbaker in estuary/flow#3021
* supabase: qualify replace_data_plane_releases DELETE for PostgREST by @skord in estuary/flow#3076
* docs: kcat recipes for testing a Dekaf topic by @jwhartley in estuary/flow#3072
* Docs: document per-prefix alert scoping and configurable thresholds by @jwhartley in estuary/flow#3039
* notifications: trial bucket retention is 20 days, not 30 by @jwhartley in estuary/flow#3074
* runtime/container: use ops::decode::Decoder for log lines by @williamhbaker in estuary/flow#3091
* data-plane-controller: remove restart flag by @qaoj in estuary/flow#3087
* runtime-next: durably seed initial connector state as {} to match V1 by @williamhbaker in estuary/flow#3081
* runtime-next: don't abandon the final capture transaction on connector EOF by @jgraettinger in estuary/flow#3090
* Docs: Remove an IP address from GCP allowed list by @jwhartley in estuary/flow#3080
* docs: remove static data plane IP list, reference dashboard by @jwhartley in estuary/flow#3097
* dekaf: improve logging by @williamhbaker in estuary/flow#3078
* agent: make startup logging consistent by @williamhbaker in estuary/flow#3098
* shuffle: make the shuffle disk limit configurable per-task by @jgraettinger in estuary/flow#3096
* runtime-next: automatically split journals under sustained append-rate throttling by @dgreer-dev in estuary/flow#3029
* billing: add tenant billing contact fields and per-tenant controller by @jshearer in estuary/flow#2902
* flowctl-go(api test): raise test-shard readiness window from ~3s to ~30s by @jshearer in estuary/flow#3101
* runtime: strip the V2 committed-close marker for derivations by @williamhbaker in estuary/flow#3100
* deps: bump aws-lc-sys and lz4_flex for security advisories by @skord in estuary/flow#2875
* data-plane-controller: fix db timeout by @qaoj in estuary/flow#3105
* agent-api: survive direct VPC egress cold-start on instance startup by @skord in estuary/flow#3109
* docs: avoiding backfills during a database failover or host change by @jwhartley in estuary/flow#3086
* docs: clarify column-level SELECT grants unsupported for MySQL/MariaDB CDC by @jwhartley in estuary/flow#3085
* json: require RFC3339 'T' separator in date-time format validator by @jacobmarble in estuary/flow#3116
* control-plane: reserve privileged tenant names by @jwhartley in estuary/flow#3083
* agent-api: harden startup and shutdown on Cloud Run by @skord in estuary/flow#3114
* Incidental fixes: gazette Append retry, connector-init image, materialize tear-down by @jgraettinger in estuary/flow#3121
* agent: flag to create new captures as runtime-v2 by @williamhbaker in estuary/flow#3107
* runtime-next: add minimum interval for post-txn triggers by @dgreer-dev in estuary/flow#3110
* flowctl: chunk API requests by url size rather than fixed count by @mdibaiee in estuary/flow#3123
* dekaf: use projection_constraints only, no constraints by @mdibaiee in estuary/flow#3124
* flowctl: normalize --prefix trailing slash to avoid misleading PermissionDenied by @GregorShear in estuary/flow#3075
* docs: retain_existing_data_on_backfill no longer requires allow_existing_tables_for_new_bindings by @jwhartley in estuary/flow#3125
* Restart Materializations sessions before IAM token expiry by @dgreer-dev in estuary/flow#3130
* docs: add "bring your own app" instructions for source-quickbooks by @nicolaslazo in estuary/flow#3135
* flowctl: re-tool `preview` on the runtime-next stack by @jgraettinger in estuary/flow#3117
* Adding support for updating the quotas when payment info changes by @bbartman in estuary/flow#3127
* control-plane-api: add unauthenticated publicDataPlanes GraphQL query by @GregorShear in estuary/flow#3129
* go/bindings: update snapshots for materialize-sqlite ser_policy by @dgreer-dev in estuary/flow#3142
* Revert "flowctl: replace `preview` with the runtime-next implementation" by @jgraettinger in estuary/flow#3141
* local: per-checkout stacks — every checkout runs its own isolated stack by @jgraettinger in estuary/flow#3137
* dekaf: log tls handshake eof instead of returning as error by @danielnelson in estuary/flow#3143
* Updating migrations to better support testing. by @bbartman in estuary/flow#3146
* shuffle: raise causal-hint stall timeout to 15m; lower prune horizon to 2GB by @jgraettinger in estuary/flow#3148
* ci: fix Platform Build by packaging Go binaries from per-checkout $GOBIN by @jgraettinger in estuary/flow#3149
* capture+materialize: Support Nonsensitive Field Overlays by @willdonnelly in estuary/flow#3119
* Docs: The Great Description-ing by @aeluce in estuary/flow#3057
* runtime: fix recursive read-lock deadlock in capture-v2 buildJoin by @jgraettinger in estuary/flow#3156
* validation: surface write-schema redact annotations on projections of split-schema collections by @GregorShear in estuary/flow#3147
* control-plane-api: retry Stripe customer search on index-lag misses by @jgraettinger in estuary/flow#3157
* flowctl: multi-shard --fixture for raw preview-next by @mdibaiee in estuary/flow#3154
* dekaf: prevent cached `TimeoutNoData` fetch responses from incorrectly signaling EOF by @jshearer in estuary/flow#3153
* mise: remove Lima VM host share and other VM tweaks by @jgraettinger in estuary/flow#3163
* docs: document connecting Azure Private Link to native Azure resources by @jwhartley in estuary/flow#3138
* docs: explain why exclusiveCollectionFilter needs few enabled bindings by @jwhartley in estuary/flow#3126

## New Contributors
* @bbartman made their first contribution in estuary/flow#3127

**Full Changelog**: estuary/flow@v0.6.10...v0.6.11

Co-authored-by: mdibaiee <mdibaiee@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

triggers: add a debounce / minimum fire interval

2 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.