Skip to content

Search: Adds re-try mechanism while re-indexing#2583

Open
Tschuppi81 wants to merge 3 commits into
masterfrom
feature/pro-1568-search-retry-on-failed-reindex
Open

Search: Adds re-try mechanism while re-indexing#2583
Tschuppi81 wants to merge 3 commits into
masterfrom
feature/pro-1568-search-retry-on-failed-reindex

Conversation

@Tschuppi81

Copy link
Copy Markdown
Contributor

Search: Adds re-try mechanism while re-indexing

A failed re-indexing will surface as an error

TYPE: Feature
LINK: pro-1568

@Tschuppi81
Tschuppi81 requested a review from Daverball July 14, 2026 12:31
@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.95652% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.50%. Comparing base (4711862) to head (4fddf26).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/onegov/search/integration.py 86.95% 3 Missing ⚠️
Additional details and impacted files
Files with missing lines Coverage Δ
src/onegov/search/indexer.py 87.09% <ø> (ø)
src/onegov/search/integration.py 93.06% <86.95%> (-0.12%) ⬇️

... and 2 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4711862...4fddf26. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Daverball Daverball 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.

I don't think the proposed fix will actually work, when a real serialization failure occurs, since it will put the database transaction into a different state, so the retries will not actually execute.

We could take an entirely different approach and make indexer serialization failures impossible by introducing a per-table lock that's shared between all processes, so the application indexers skip writing changes to that table, while the full reindex is in progress for that same table. Although it's probably possible for the skipped entries to become desynced that way, so it's probably better that those serialization failures happen. If we delay indexing instead of skipping it, we could probably cause the serialization failure to always happen in a regular request, instead of during indexing, but it may slow down some requests significantly while indexing is in progress and we potentially risk the lock getting stuck if reindexing crashes.

and sqlstate.startswith('40')
and attempt < REINDEX_MAX_ATTEMPTS
):
index_log.info(

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.

I'm not quite sure what will happen to the raw connection here with a real database error, since we no longer use a session.begin_nested in the indexer.

I think it will probably be in an invalid state and will refuse to execute any further queries. So this fix wouldn't actually help right now. I'm not sure if doing a session.execute('ROLLBACK') here would be safe, since we're on a different thread we should be getting our own session, but I'm not sure if that means we're necessarily also getting our own raw connection.

Maybe manually doing tx = session.begin_nested() before processing the indexer tasks and then calling tx.rollback() in this branch would work, but I'm not sure.

Your mocked test case doesn't exercise the details here properly, since you're completely bypassing the indexer, when you emit an exception, so to the database it still looks like you only emitted the query once, and that it succeeded.

I'm not sure if there is a robust way to test this. You might be able to trigger a real serialization failure reliably by using two threads and some events for synchronization.

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.

Savepoints/Nested transactions will probably not work, since the start of the main transaction will be the same, so the database will still consider the retry a serialization failure. We probably need to hope here that SQLAlchemy properly isolates the connections in the pool and that a manual session.execute('ROLLBACK') is safe.

Comment on lines +307 to +310
finally:
session.invalidate()
if session.bind and hasattr(session.bind, 'dispose'):
session.bind.dispose()

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.

I kind of missed that this finally was happening inside the loop. It's a little bit wasteful using a new connection for each iteration, but it's probably fine for error recovery in this case.

This is also the only reason it was working, even though I thought it shouldn't, since invalidating the session and disposing of the connection implicitly rolls back the transaction and starts a new transaction in the next iteration.

You could try moving this out of the loop and changing the returns inside the loop to break and emitting an explicit session.execute(text('ROLLBACK')) before you continue.

You would need to be a little careful about BaseException for things like KeyboardInterrupt, but since we're tearing the entire application down in those cases, the connections should still get cleaned up.

A different approach, that preserves the old semantics of cleanup always happening, even with BaseException, would be wrapping the entire loop in a try: finally: block and move the cleanup into that finally block. That's what I thought was going on already.

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.

2 participants