Skip to content

load-backup-archives: restore collections asynchronously and verify completion#71

Open
OmarB97 wants to merge 9 commits into
metabrainz:masterfrom
OmarB97:fix/load-backup-archives-async-restore
Open

load-backup-archives: restore collections asynchronously and verify completion#71
OmarB97 wants to merge 9 commits into
metabrainz:masterfrom
OmarB97:fix/load-backup-archives-async-restore

Conversation

@OmarB97

@OmarB97 OmarB97 commented Jun 10, 2026

Copy link
Copy Markdown

Problem

load-backup-archives can report success while leaving large collections silently empty. Three compounding issues:

  1. The synchronous Collections API RESTORE call hard-times-out server-side after 180 seconds (restore the collection time out:180s, SolrException, HTTP 500). Large collections — release-group, url, and especially recording — routinely exceed that on modest hardware; artist/release only survive when they finish under the limit.
  2. The status check [[ ! "$(... | jq .responseHeader.status)" =~ 0 ]] is a substring match, and "500" contains "0", so the timeout error response passes the check.
  3. The script then immediately deletes the extracted backup files — out from under the restore that is still running in the background on the Solr side — so the restore aborts and the collection ends up empty with a clean-looking log.

Observed live on a musicbrainz-docker deployment (2026-06): after a full load-backup-archives run that logged success for all 14 collections, release-group and url had numDocs=0 while artist (2.89M) and release (5.55M) were fine. Re-running those two restores with &async= + REQUESTSTATUS polling loaded them correctly (4.33M and 19.6M docs).

Fix

Submit the restore with async=<request-id>, poll REQUESTSTATUS every 10s until a terminal state, fail loudly on failed/notfound, and only after completed clean up the request status and the extracted backup files. The submit-time status check is now an exact comparison.

Happy to adjust style/poll cadence to project preferences.

…ompletion

The synchronous Collections API RESTORE call hard-times-out after 180
seconds server-side, which large collections (release-group, url,
recording) routinely exceed on modest hardware. The status check then
accepted the error response anyway, because '=~ 0' is a substring match
and "500" contains "0"; the extracted backup files were deleted from
under the still-running background restore, leaving those collections
silently empty (artist and release survived only by finishing under 180s).

Submit the restore with async=<id>, poll REQUESTSTATUS until a terminal
state, fail loudly on failed/notfound, and only then clean up the request
status and the extracted files.
@yvanzo

yvanzo commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

@OmarB97: First of all, thanks a lot for this much needed fix. I will review it more in-depth later on this week.

yvanzo added 8 commits July 1, 2026 12:38
Printing to stderr seems to be more readable like that.

Reference: https://www.shellcheck.net/wiki/SC2005
Beyond the three restore states (`completed`, `failed`, and `notfound`)
explicitly handled already, two other states (`running` and `submitted`)
are silently handled too. However, it doesn’t handle unexpected restore
states due to either future changes in Solr or transient network issues.

This patch makes handling all known restore status explicitly, to allow
handling unexpected restore states too. It will also allow reporting
progress in a separate amending commit.

Note that when the output of `REQUESTSTATUS` doesn’t contain any
`.status.state` key, then `jq` will return the `null` string.

References:
https://github.com/apache/solr/blob/releases/solr/9.7.0/solr/solrj/src/java/org/apache/solr/client/solrj/response/RequestStatusState.java#L28-L41
https://solr.apache.org/guide/solr/latest/configuration-guide/collections-api.html#requeststatus
https://jqlang.org/manual/#object-identifier-index
While the synchronous requests were timing out too soon due to a hard
server-side time limit of 180s, it is still safer to have a time limit
for the asynchronous requests, preferably client-side and customizable.

This patch limits the number of polling to 360. That number can be
changed through the environment variable `MUSICBRAINZ_SEARCH_MAX_POLL`.
With 10s between each polling, it will stop after one hour at least.
Clarify that this check is a string comparison with the right-hand side.
This comment is in git commit message already.
It is about previous bugged code only.
To better avoid conflicts with stalled requests from previous attempts,
this patch adds the current timestamp to the asynchronous request ID.
It also prefixes each number with a descriptive abbreviation.
A delete status failure is most likely not blocking, for example if the
asynchronous request ID already expired. Still, it is worth warning
about it to help with debugging in case of serious issues.

Reference: https://solr.apache.org/guide/solr/9_7/configuration-guide/collections-api.html#deletestatus
Restoring large collections such as recordings can take several minutes.
To help with monitoring the progress during that time, this patch logs a
progress message about every minute, actually every 6 polling attempts.

@yvanzo yvanzo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry that it took a couple more weeks.

I amended your work with a few changes, including:

  • Handling of unexpected restore status states
  • Customizable client-side timeout to abort the whole process when the server isn’t restoring a collection in the expected time, set to 1h for now. (It does take 6m on our test server for the recording collection.)

I tested these changes by reloading backup archives in a local mirror.

Now just requesting a peer-review.

@yvanzo
yvanzo requested a review from mwiencek July 1, 2026 14:16
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