Skip to content

feat(ingestion): derive the watermark from Postgres - #848

Merged
jirhiker merged 1 commit into
stagingfrom
feat/postgres-watermark
Aug 19, 2026
Merged

feat(ingestion): derive the watermark from Postgres#848
jirhiker merged 1 commit into
stagingfrom
feat/postgres-watermark

Conversation

@jirhiker

Copy link
Copy Markdown
Member

Task 3.5. automated_ingestion/shared/watermark.py, seven tests.

Stacked on #847 — the tests load observations through the upsert loader, so
this targets that branch rather than staging. Merge #847 first and this
retargets cleanly.

Derived, not stored

Aqueduct keeps watermarks in a GCS object beside the raw zone, because FROST
cannot be queried cheaply for a maximum. Ocotillo's destination can, so the
watermark is simply MAX(observation_datetime) for the series.

That is a deliberate divergence, recorded in the module docstring so it reads as
a decision rather than an oversight. A stored watermark is a second source of
truth about what was loaded, and the two drift: a half-succeeded load, or a
sidecar write that fails after the rows commit, leaves it claiming more or less
than the data holds.

The payoff: "backfill never advances the normal watermark" stops being a rule
to enforce and becomes a property that cannot be violated — re-loading a window
behind the maximum cannot move a maximum forward. Task 4 leans on this, so it is
asserted anyway, in both directions: older data, and the same window twice.

Keyed by thing, not deployment

The plan said (thing_id, parameter_id) and that is right, for a reason worth
stating. Observations carry deployment_id, but a series outlives its hardware:
replacing a diver creates a new deployment for the same well, and a watermark
keyed to the deployment would report nothing for the new one and re-fetch the
entire history. The query joins through deployment.

This is the same column confusion that made the 3.4 constraint wrong in the
plan, landing the other way round.

The floor is not a lever

resolve_start falls back to initial_start_date only for a series never
loaded. A test asserts a floor ahead of the watermark does not win either — so
it cannot be used to skip forward any more than to backfill. Re-fetching history
is what the backfill jobs are for.

Session sharing

PostgresWatermarkStore takes the session the loader is using, so the watermark
reflects that session's committed state rather than another connection's
snapshot. That matters once a run loads and then asks where it got to.

🤖 Generated with Claude Code

Aqueduct keeps watermarks in a GCS object because FROST cannot be queried
cheaply for a maximum. Ocotillo's destination can, so the watermark is
MAX(observation_datetime) for the series.

That is a deliberate divergence. A stored watermark is a second source of truth
about what was loaded, and the two drift: a half-succeeded load, or a sidecar
write that fails after the rows commit, leaves it claiming more or less than the
data holds. A derived one cannot disagree with reality.

It also turns "backfill never advances the normal watermark" from a rule to
enforce into a property that cannot be violated -- re-loading a window behind
the maximum cannot move a maximum forward. Tested in both directions anyway,
older data and the same window twice, since the claim is load-bearing for
task 4.

Keyed by thing rather than deployment. Observations carry deployment_id, but a
series outlives its hardware: replacing a diver creates a new deployment for the
same well, and a watermark keyed to the deployment would report nothing for the
new one and re-fetch the entire history.

The floor applies only to a series that has never been loaded, in either
direction -- a floor ahead of the watermark does not win either, so it cannot be
used as a backfill lever by accident.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Your pull request is automatically being deployed to Dagster Cloud.

Location Status Link Updated
ocotillo-automated-ingestion View in Cloud Aug 19, 2026 at 06:07 AM (UTC)

Base automatically changed from feat/transducer-upsert-loader to staging August 19, 2026 06:05
@jirhiker
jirhiker merged commit 87b8e9c into staging Aug 19, 2026
8 checks passed
@jirhiker
jirhiker deleted the feat/postgres-watermark branch August 19, 2026 06:05
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.

1 participant