Repository navigation
SOLR-18391: Clean up a collection when creation fails partway, and name the cause in the error - #5027
Open
nick-boss-tech wants to merge 10 commits into
Open
nick-boss-tech wants to merge 10 commits into
nick-boss-tech wants to merge 10 commits into
Conversation
…er error The alias write is the last step of a create, after the collection itself is complete, and a transient ZooKeeper error there failed the whole create and deleted the new collection. The write now goes through a small bounded retry (3 attempts, a short pause) that only retries ZooKeeper errors; a failure that survives the retries, and any other failure, propagates exactly as before. An interrupt is never retried. The retry helper is unit-tested directly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 AI text below 🤖 (posted on behalf of Nick Shanin)
https://issues.apache.org/jira/browse/SOLR-18391
A create that fails after the collection state has been written now deletes the collection before the error returns (the cases where it cannot are under Limits), and an unexpected failure returns a message that names the cause instead of a null message. Today that cleanup runs for only two kinds of failure, and any other failure leaves behind a collection with no replicas that blocks a retry under the same name. This is the broader route suggested on #4997, which stays as a draft and is superseded by this PR. We would like your view on the three choices below, the main one being whether a failed alias write at the end of a create should delete a collection that is otherwise working.
What happens today
When collection creation fails after the collection state has been written, whether anything is cleaned up depends on the type of the exception. Only two failures run the delete that removes the half-created collection: an AssignmentException out of replica assignment, and a core that fails to come up. Any other failure, for example a NullPointerException when the placement strategy reads a cluster state that does not contain the new collection yet, lands in a catch-all that throws a SolrException with a null message, and the collection stays behind with no replicas. The client cannot tell what failed, and the leftover collection blocks a retry under the same name. The two waits in the create (collection visible, replica entries visible) also leave the collection behind when they time out.
What this change does
Could not create collection <name>: <cause>(CreateCollectionCmd.java:526) instead of a null message, and the message for a core that fails to come up now names the first per-node failure (CreateCollectionCmd.java:445, :471-477).collection already exists: <name>and can never be deleted by a create that did not make it.Proof
The new cleanup tests are real-cluster tests with failure injection through a delegating placement plugin factory; the retry helper is covered by unit tests. With the production code at the base (c3cdf7b) and the tests from this branch, leaving out CreateCollectionCmdRetryTest because it calls the new helper and does not compile on base, exactly two tests fail, in the ticket's shapes: testCleanupAfterUnexpectedPlacementFailure (the client gets a SolrException with an empty message) and testAssignForMissingCollection (a NullPointerException instead of an AssignmentException). testCleanupAfterPlacementException passes on base, because main already cleans up after an AssignmentException.
Verified locally 2026-10-05 at fca4235 (the PR head differs from it only by one later commit that removes an internal handoff document): CreateCollectionCleanupTest 4/4 (CreateCollectionCleanupTest.java), PlacementPluginIntegrationTest 8/8 (PlacementPluginIntegrationTest.java), OverseerCollectionConfigSetProcessorTest 26/26 (OverseerCollectionConfigSetProcessorTest.java), DeleteCoreRemnantsOnCreateTest 5/5 (DeleteCoreRemnantsOnCreateTest.java), CreateCollectionCmdRetryTest 4/4 (CreateCollectionCmdRetryTest.java), and :solr:core:check -x test green, with tidy and the Error Prone compile clean. Fork CI runs of all five suites on that same tree are also green.
Round 29 follow-up, verified locally 2026-10-07 at adcda10. The exists guard now has a test, testCreateDoesNotDeleteExistingCollectionOnStaleView in CreateCollectionCleanupTest, which invokes the command with a cluster state that does not contain the collection while ZooKeeper already holds its state.json, and asserts the create fails with BAD_REQUEST and the state.json is byte-identical afterwards. With the exists check disabled in a scratch run, the same test fails because the create's cleanup deletes the pre-existing collection, so the test pins the guard. The failure detail in the core-creation error now names only the first failure instead of dumping the whole failure map into the client message. CreateCollectionCleanupTest 5/5, CreateCollectionCmdRetryTest 4/4, PlacementPluginIntegrationTest 8/8, DeleteCoreRemnantsOnCreateTest 5/5, and :solr:core:check -x test green, with tidy and the Error Prone compile clean. The base re-run was also repeated at c3cdf7b with the tests from this branch: testCleanupAfterPlacementException passes on base, as stated above, and testCleanupAfterUnexpectedPlacementFailure is the one that fails.
Choices to check
Three decisions we made that you may prefer made the other way. Each item ends with what we would change.
Alias failure deletes the collection. If the alias write (CreateCollectionCmd.java:500-507) still fails after the bounded retry, the cleanup (CreateCollectionCmd.java:515-516) deletes a collection that was created successfully and is otherwise working, because the create as a whole is reported as failed. One case differs: if an alias attempt was applied and the retries still end in an error, the cleanup's delete is refused because the collection is referenced by an alias, so the collection and the alias both stay and the client gets an error (DeleteCollectionCmd.java:237-244). The alternative is to bound the cleanup at activation, so a failure that late leaves the collection in place and only the alias step is reported. We kept the delete: a create that reports failure should not leave behind a collection the client believes was never created. If you prefer the activation bound, we are willing to make that change in this PR.
The exists guard sits in the non-PRS branch only. The deletion risk it closes does not exist in the PRS branch, where the state write (CreateCollectionCmd.java:211-212) throws before the flag is set, so PRS was left as it was. Two inconsistencies remain there: a pre-existing collection missed by the up-front check fails with a 500 carrying the NodeExistsException text instead of the new 400 (CreateCollectionCmd.java:233-236), and a lost reply on the PRS state write (CreateCollectionCmd.java:399-402) can leave a full state.json with no replicas. Moving the guard above the PRS branch split closes both. We shipped it as built to keep this change small; we are willing to do the move in this PR if you prefer the branches to behave the same.
Two things left as on main. The cleanup catch (CreateCollectionCmd.java:510) covers Exception, not Error, so an Error still skips the cleanup, as on main. The message for the 30 s wait (
Could not fully create collection: <name>, CreateCollectionCmd.java:224 and CreateCollectionCmd.java:259) is unchanged and does not say what timed out. If you want either in this PR, we would catch Throwable for the cleanup and rethrow, and reword that message to name the wait.Other behavior changes
Limits
We limited the scope of this PR on purpose. Several of the points above could be closed here or in a follow-up, and we are glad to do either, so please tell us which of them you want in this PR. The alias retry and the two stale-state workarounds (the cluster-state fill-in and the placement strategy guard) are independently revertible parts of the change; reverting any of them does not affect the cleanup rework.
Changelog:
changelog/unreleased/SOLR-18391-collection-creation-failure-cleanup.ymlAI assistance
AI agents assisted with research, implementation, review, and drafting. Nick Shanin directed the work and takes responsibility for this contribution.