Skip to content

fix(index): deactivateIndex must not orphan the OS store when the last index is removed (#36501)#36502

Merged
fabrizzio-dotCMS merged 1 commit into
mainfrom
issue-36501-deactivate-index-empty-os-store
Jul 13, 2026
Merged

fix(index): deactivateIndex must not orphan the OS store when the last index is removed (#36501)#36502
fabrizzio-dotCMS merged 1 commit into
mainfrom
issue-36501-deactivate-index-empty-os-store

Conversation

@fabrizzio-dotCMS

@fabrizzio-dotCMS fabrizzio-dotCMS commented Jul 9, 2026

Copy link
Copy Markdown
Member

Proposed Changes

Fixes the first product finding tracked in #36501, surfaced while enabling phased integration runs (#36320).

ContentletIndexAPIImpl.deactivateIndex(String) rebuilds the OpenSearch VersionedIndices store without the deactivated slot. When that slot was the last populated one, the rebuilt record is empty and VersionedIndicesAPI.saveIndices rejects it by contract ("At least one index must be specified"). Under a non-zero migration phase this caused:

Site Phase Before
phase-3 branch 3 (OS primary, no try/catch) exception propagateddeactivateIndex failed hard 🔴
phase-1/2 branch 1 & 2 (best-effort mirror) exception swallowed → stale/dangling store row left behind 🟠 (same failure mode as #35640)

The stale row is exactly what clearOsStorePointer was written to prevent for #35640: on the next restart initOSCatchup would treat it as authoritative and recreate the deleted index empty.

Fix

  • Extract the OS-mirror rebuild into a shared private helper mirrorDeactivateToOsStore(indexName), deduplicating the near-identical phase-3 and phase-1/2 blocks.
  • The helper applies the same guard as clearOsStorePointer: if the rebuild is empty, remove the version row (removeVersion(OPENSEARCH_3X)) instead of saving an empty record. This gives the OS versioned store the same "empty = OK" semantics the legacy ES path already has.

Test

  • DeactivateIndexEmptyStoreIT (registered in OpenSearchUpgradeSuite) — deactivates the last index under phase 3 (asserts no throw + store cleared) and phase 1 (asserts no stale row). Verified locally: Tests run: 2, Failures: 0.

Checklist

Related: #36501 (tracking), #36320 (phase-testing tooling), #35640 (original store-pointer guard).

This PR fixes: #36501

…t index is removed (#36501)

When the deactivated slot was the last populated one in the OpenSearch
VersionedIndices store, the rebuilt record is empty and saveIndices rejects it
by contract ("At least one index must be specified"). Before this change that
meant, under a non-zero migration phase:

- Phase 3 (OS primary, no try/catch): the exception propagated and
  deactivateIndex failed hard.
- Phases 1/2 (best-effort OS mirror): the exception was swallowed with a
  warning, leaving a stale/dangling store row that initOSCatchup would treat as
  authoritative on the next restart (same failure mode as issue #35640).

Extract the OS-mirror rebuild into a shared helper (deduplicating the phase-3
and phase-1/2 blocks) that removes the version row instead of saving an empty
record — parity with clearOsStorePointer's #35640 guard.

Adds DeactivateIndexEmptyStoreIT (registered in OpenSearchUpgradeSuite) covering
deactivate-last-index under phase 3 (no throw, store cleared) and phase 1 (no
stale row). Found while enabling phased integration runs (#36320).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @fabrizzio-dotCMS's task in 1m 27s —— View job


Rollback-Safety Analysis

  • Read docs/core/ROLLBACK_UNSAFE_CATEGORIES.md
  • Get full PR diff (27191bc6...18c767e6)
  • Analyze diff against every unsafe category
  • Post verdict

Verdict: No unsafe category matched.

Summary of what changed:

  • ContentletIndexAPIImpl.java: pure refactor — extracts the existing OS-mirror rebuild logic (already present in both the phase-3 and phase-1/2 blocks) into a private helper mirrorDeactivateToOsStore(String), and adds a guard: when the rebuilt VersionedIndices is empty, it calls the pre-existing VersionedIndicesAPI.removeVersion(OPENSEARCH_3X) (already used by clearOsStorePointer for [QA-G6] OpenSearch Migration — Index Delete: ES vs OS Behavior (TC-016–TC-020) #35640) instead of trying to saveIndices an empty record.
  • DeactivateIndexEmptyStoreIT.java: new integration test only.
  • OpenSearchUpgradeSuite.java: registers the new test in the suite only.

Checked against every category in the reference doc:

  • No runonce DB migration task, no schema change → C-1, H-1, H-2, H-3, H-7 don't apply.
  • No ES mapping change (ESMappingAPIImpl/ESMappingUtilHelper/putMapping) → C-2 doesn't apply.
  • No contentlet_as_json/CURRENT_MODEL_VERSION touch → C-3 doesn't apply.
  • No DROP TABLE/DROP COLUMN/DROP PROCEDURE/DROP FUNCTION → C-4, H-6 don't apply.
  • No new ContentType field type, no storage-provider change → H-4, H-5 don't apply.
  • No VTL viewtool / returned-object contract change → H-8 doesn't apply.
  • No column type change, bundle format change, REST/GraphQL contract change, or OSGi interface change → M-1 through M-4 don't apply.

removeVersion(String) is not new API — it's an existing VersionedIndicesAPI/IndicesFactory method already exercised by clearOsStorePointer (#35640). N-1 already tolerates an absent/empty OS VersionedIndices row (that's the normal pre-migration-activation state), so a rollback to N-1 after N has deleted an empty row does not leave N-1 in an unreadable or crashing state — it just re-exposes the original bug this PR fixes. No manual DB/ES intervention required either way.

This PR is safe to roll back.

@mergify

mergify Bot commented Jul 13, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@fabrizzio-dotCMS fabrizzio-dotCMS added this pull request to the merge queue Jul 13, 2026
Merged via the queue into main with commit b621067 Jul 13, 2026
62 checks passed
@fabrizzio-dotCMS fabrizzio-dotCMS deleted the issue-36501-deactivate-index-empty-os-store branch July 13, 2026 17:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

bug(index): product fixes & findings from phased (ES→OS) integration runs

3 participants