Repository navigation
fix: honor the configured lookback_window when filtering client-side incremental records - #1182
Conversation
…incremental records
…ons with the lookback window
…he lookback floor comment
👋 Greetings, Airbyte Team Member!Here are some helpful tips and reminders for your convenience. 💡 Show Tips and TricksTesting This CDK VersionYou can test this version of the CDK using the following: # Run the CLI from this branch:
uvx 'git+https://github.com/airbytehq/airbyte-python-cdk.git@bazarnov/fix-lookback-client-side-filter#egg=airbyte-python-cdk[dev]' --help
# Update a connector to use the CDK from this branch ref:
cd airbyte-integrations/connectors/source-example
poe use-cdk-branch bazarnov/fix-lookback-client-side-filterPR Slash CommandsAirbyte Maintainers can execute the following slash commands on your PR:
|
Anatolii Yatsuk (tolik0)
left a comment
There was a problem hiding this comment.
Approving. The fix is correct. should_be_synced now uses the same lower bound that stream_slices requests, and streams without a lookback stay on the old return self.start path. The start-date clamp and the OverflowError branch are both covered by tests. I also reverted the fix locally, and the unit, plain-stream, and data-feed lookback tests went red.
One request before merge: please update the "Behaviour change for connectors" section of the description. Release notes are built from it, and it currently leaves out some affected connectors. I scanned every manifest.yaml in the monorepo for a DatetimeBasedCursor that sets both lookback_window and is_client_side_incremental (with $refs resolved). No data feed sets a lookback, so that part of the description is right. These are the client-side streams:
| Connector | Streams | Lookback |
|---|---|---|
source-snapchat-marketing |
8 streams on their own cursor, plus the parent cursors of 19 stats/child streams | P2D, hard-coded (already listed) |
source-app-store-connect |
customer_reviews_per_app |
P7D, hard-coded, so it is always affected and gets the largest jump per sync. Not listed. |
source-gorgias |
events |
PT5M, hard-coded (already listed) |
source-intercom |
conversation_parts, company_segments (listed), plus companies, contacts, conversations, segments |
from config, default 0 |
source-gong |
callTranscripts (global_substream_cursor: true) |
from config (lookback_window_days), default 0. Same shape as #1014, so this PR fixes it too. Not listed. |
source-linkedin-ads |
account_users, campaign_groups, campaigns, conversions, creatives, plus the parent cursors of 11 analytics streams and videos |
from config, default 0. Not listed. |
When the config-driven windows are left at their default, they evaluate to P0D, which gives a falsy timedelta(0). Those streams only change for users who set a lookback.
Two optional nits:
_lowest_cursor_value_to_sync()runs once per record, but all of its inputs are fixed after__init__, so it could be computed once.- In
test_client_side_incremental_lookback.py, the incremental-parent test checks state with>=. Exact values (_STATE_VALUE + 10or_STATE_VALUE) would also catch a state that moves too far forward.
Summary
ConcurrentCursor.should_be_syncedfiltered records fromself.start, ignoring the configuredlookback_window, whilestream_slicesrequests records from the state minus the lookback window. So for client-side incremental streams (is_client_side_incremental: true) the CDK asked for the lookback window and then dropped it: records whose cursor value is behind the state but inside the lookback window were silently lost.The effect is worst with
global_substream_cursor: true: the global cursor is the max across all partitions, so records of a partition that sit slightly behind it are dropped, which is the missingconversation_partsreported forsource-intercomin #1014. The same gap also affects plain and per-partition client-side streams, and parent streams withincremental_dependency: true, where a dropped parent record means its child partition is never requested.This is a regression from #679 (released in 6.60.4). Before it, the legacy
DatetimeBasedCursor.should_be_syncedusedmax(start_datetime, state - lookback_window).Changes:
ConcurrentCursor.should_be_syncednow keeps records from the state minus the lookback window, never before the configured start date, which is the same lower boundstream_slicesrequests. Nothing changes whenlookback_windowis unset or zero.is_client_side_incrementalandis_data_feeddescriptions indeclarative_component_schema.yaml(and the two matching strings in the generated models) now describe the lookback window.Behaviour change for connectors
Only streams that combine
lookback_windowwithis_client_side_incrementaloris_data_feedare affected. They emit the records inside the lookback window again, as they did before 6.60.4. State never moves backwards: the emitted state is still the max synced cursor value.source-snapchat-marketing(P2D) andsource-gorgiasevents(PT5M).incremental_dependency: trueand a lookback produce child partitions for parent records inside the window, so they make more child requests (source-intercomconversation_partsandcompany_segmentswhenlookback_windowis set).Tests
test_cursor.py: records inside the lookback kept and older ones dropped; the lookback never keeps records before the configured start; state below the configured start keeps the previous floor; thestart=NoneandOverflowErrorbranches.test_client_side_incremental_lookback.py(new, end to end): plain, per-partition and global substream cursors with state, with and without a newer record, asserting emitted records and state (including per-partitionstates); the no-state case; an Intercom-shaped parent withincremental_dependencyand a global child cursor.test_data_feed_integration.py: a data feed with a lookback paginates through the window and emits its records.max(lower, self._start)or removing it turns the matching guard tests red.Thanks to Praveen Kumar Mittal (@mittalpk), whose analysis and branch in #1014 pointed at the same method. This version adds the start-date clamp (without it, records older than the start date were emitted on the first sync) and the end-to-end coverage.
Fixes #1014