You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(opensearch): repair bare orphan index on bootstrap without discarding populated indices (#36237) (#36353)
## Problem
Re-fix for **#36237** (QA failed the prior PRs #36238/#36240 on
**TC-003**).
The idempotent-bootstrap reuse path introduced in #36238 re-asserted the
custom mapping via `putMapping` against an orphaned cluster index (one
that exists in the cluster but is missing from the dotCMS index store).
On a **bare** orphan this failed:
```
INFO Bootstrap: OS index already exists, reusing and re-asserting mapping: ...working_….os
ERROR MappingOperationsOS - putMapping failed for index ...working_….os — HTTP 400 (×8)
```
Root cause: the content mapping references the custom analyzer
`my_analyzer`, defined in `os-content-settings.json`. Analyzers are
**static** index settings that can only be applied at index creation —
so a `putMapping`-only re-assert against a bare index is rejected
(`analyzer [my_analyzer] not found`) and the index is left half-mapped,
with the dotCMS dynamic templates missing.
## How the orphan arises
In the migration catch-up path the OS index name is **derived
deterministically** by mirroring the ES name (`working_T0` →
`cluster_X.working_T0.os`), not generated with a fresh timestamp. If a
prior bootstrap created that physical index but crashed before
committing its `VersionedIndices` store pointer, the next restart
re-derives the **same** name, finds it already in the cluster, and the
create fails with `resource_already_exists`.
## Fix
Decide the orphan's fate by **document count**, so a populated index is
never discarded:
- **Empty orphan (0 docs)** → delete and recreate from scratch,
restoring full **settings + base mapping + custom mapping**. An empty
index has no data and no reindex progress, so recreating it costs
nothing operationally — and it's the only case `putMapping`-reuse could
not repair.
- **Populated orphan (>0 docs), or unknown count** → reuse in place,
**untouched** (not deleted, not recreated, not remapped). A
dotCMS-created index already carries the full mapping; deleting it would
force a full reindex (hours, degraded/inconsistent search) — not
justified to clean up an orphan. On any uncertainty (the count probe
fails) we err toward reuse.
The delete only ever fires against a **demonstrably empty** orphan, and
only in the bootstrap path for a store slot that is not registered —
never against the active production index.
## Tests
- **Unit** (`ContentletIndexAPIImplBootstrapTest`, 8/8): empty→recreate,
populated→reuse-untouched, doc-count-probe-fails→reuse,
empty-orphan-delete-fails→still-creates, missing→create,
create-fails→no-mapping, existence-probe-fails→create, OS-tag
propagation.
- **Integration** (`ContentletIndexAPIImplMigrationIntegrationTest`):
new regression IT creates a **bare** orphan against a real OpenSearch
cluster, runs the bootstrap seam, and asserts the recreated index
carries the dotCMS dynamic templates (`template_1`) **and** the
`my_analyzer` setting. Verified green against OpenSearch:
```
Tests run: 23, Failures: 0, Errors: 0, Skipped: 6 → BUILD SUCCESS
✅ bootstrap bare-orphan Phase 1 — recreated with full settings + mapping
```
Closes#36237
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0 commit comments