Skip to content

Fix/3994 sharepoint incremental sync skip sites - #4256

Open
falscherwiener1-svg wants to merge 7 commits into
elastic:mainfrom
falscherwiener1-svg:fix/3994-sharepoint-incremental-sync-skip-sites
Open

Fix/3994 sharepoint incremental sync skip sites#4256
falscherwiener1-svg wants to merge 7 commits into
elastic:mainfrom
falscherwiener1-svg:fix/3994-sharepoint-incremental-sync-skip-sites

Conversation

@falscherwiener1-svg

Copy link
Copy Markdown

What

Set check_timestamp=False on the sites() call in get_docs_incrementally so that all sites are visited during incremental sync.

Closes #3994

Why

Drive item deletions and modifications do not reliably update the parent site's lastModifiedDateTime in the Graph API. With check_timestamp=True, entire sites are skipped — deleting drive items and modifications are silently lost until the next full sync.

How

The codebase already uses this pattern for site_drives() (line 705):

# Edit operation on a drive_item doesn't update the
# lastModifiedDateTime of the parent site_drive. Therefore, we
# set check_timestamp to False when iterating over site_drives.

Checklist
[ X ] Unit tests pass

Add per-item retry logic for transient HTTP status codes (429, 503)
in the bulk ingestion sink. Previously, individual items that failed
with transient errors inside a successful bulk response were sent
directly to the ErrorMonitor without retry, which could trigger a
premature TooManyErrors abort on a temporarily overloaded cluster.

Changes:
- Add TRANSIENT_STATUS_CODES constant (429, 503)
- Add _determine_action() helper to extract the bulk action key from a
  response item
- Add _extract_transient_failed_operations() to collect retryable
  items from a bulk response
- Add _retry_transient_item_failures() with exponential backoff that
  retries only the failed items
- Rewire _batch_bulk to call _retry_transient_item_failures before
  _process_bulk_response, so the ErrorMonitor only tracks failures
  after all retry attempts are exhausted
- Remove the now-unused retryable import and the dead _bulk_api_call
  function that was never invoked

Closes elastic#4002
…ns (elastic#4222)

SharePoint list GUIDs are only unique within a site collection.
When two site collections share a list GUID (e.g. via site templates
or migration), list_item documents from different sites collide on
the same _id and silently overwrite each other.

Include site_id in the _id construction to guarantee global uniqueness.
…led (elastic#4139)

The scheduling loop logs an ERROR every ~30s when a connector
supports DLS but the Elasticsearch license is not Platinum.
This is misleading when the user has not enabled access control
sync at all.

Only check the license when access control sync scheduling is
actually enabled by the user. If it's disabled, skip silently.
…lastic#3994)

Drive item deletions and modifications do not reliably update
the parent site's lastModifiedDateTime in the Graph API. When
check_timestamp=True, entire sites are skipped during incremental
sync, silently losing drive item changes.

Align with the existing pattern used for site_drives: set
check_timestamp=False so all sites are visited, ensuring no
changes are missed.
@erikcurrin-elastic

Copy link
Copy Markdown

This PR incorporates the previous two (4254 and 4255). I assume you only want review of this one?


return failed_ops

async def _retry_transient_item_failures(self, operations, pipeline_name):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

loses first-call successes from the return value. After a retry, res contains only the retried items. _batch_bulk then calls _process_bulk_response(res, ids_to_ops) where ids_to_ops maps all original operations, but res only has the retried subset. Items that succeeded on the first call won't be tallied in stats. Was that intentional?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch, that was not intentional. Items that succeeded on the first call were silently dropped from the returned response, which meant _process_bulk_response and _populate_stats never saw them.

I have updated _retry_transient_item_failures to track successful items across all calls and return a merged response containing both the originally successful items and the final retry outcome. See commit 564533c.

if connector.features.document_level_security_enabled():
if (
connector.features.document_level_security_enabled()
and connector.access_control_sync_scheduling.get("enabled", False)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Guard against access_control_sync_scheduling not being set

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yep, that is the intent. This guards against the case where DLS is enabled but access control sync scheduling has not been explicitly turned on. The .get("enabled", False) defaults to off, so the license check is only hit when the feature is actually configured.

site_collection["siteCollection"]["hostname"],
self.configuration["site_collections"],
check_timestamp=True,
check_timestamp=False,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do we have to worry about site_lists and site_list_items also?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good question. site_lists and site_list_items do not need this change, the issue is specific to the drive item path.

Drive items are fetched via delta links (Graph API delta queries, see self.client.drive_items() at line 717). The parent site/site_drive are only traversal containers to reach the drives. Since editing a drive_item does not update the parent lastModifiedDateTime, we must skip the timestamp check to always reach the delta-enabled drives.

Lists, list items, and pages do not use delta queries, they are fetched directly and rely on lastModifiedDateTime comparison against self.last_sync_time(). Modifying a list item does update the list item own lastModifiedDateTime, and creating/updating a list does update the list lastModifiedDateTime. So check_timestamp=True is correct for those.

The same reasoning already existed for site_drives() (line 705 comment), this PR just applies the same logic one level up to sites().

Previously, each retry call overwrote  with only the retried
subset, so items that succeeded on the first call were silently
dropped from the returned response. This caused _process_bulk_response
and _populate_stats to never see those items.

Now successful items are tracked across all calls and a merged
response is returned containing both the originally successful
items and the final retry outcome.

Addresses review feedback on elastic#4256.
@falscherwiener1-svg

Copy link
Copy Markdown
Author

@erikcurrin-elastic Yes, that is correct. PRs #4254 and #4255 have been closed in favor of this single PR which incorporates all changes. Please review only this one (#4256).

I have also addressed the retry feedback in sink.py — see commit 564533c and the inline reply.

@Jan-Kazlouski-elastic
Jan-Kazlouski-elastic self-requested a review July 29, 2026 09:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[SharePoint Online] Incremental sync skips sites by timestamp, silently dropping drive item deletions and modifications

2 participants