fix: Optimize cleanup jobs, eliminate raw ES client usage, add test coverage - #2267
fix: Optimize cleanup jobs, eliminate raw ES client usage, add test coverage#2267niemyjski wants to merge 19 commits into
Conversation
154223e to
7788c75
Compare
There was a problem hiding this comment.
Pull request overview
This PR refactors cleanup jobs to rely on repository abstractions, improves duplicate stack cleanup behavior, and adds integration coverage for cleanup/repository operations.
Changes:
- Reworked orphaned-data and cleanup jobs with lock renewal, cancellation checks, and repository-based deletes/updates.
- Added event repository helpers for bulk deletion, stack reassignment, and distinct-id aggregation.
- Added integration tests for cleanup pagination, retention, duplicate signatures, and event repository operations.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/Exceptionless.Core/Jobs/CleanupDataJob.cs |
Extends lock duration and renews locks during paged cleanup/retention loops. |
src/Exceptionless.Core/Jobs/CleanupOrphanedDataJob.cs |
Replaces direct Elasticsearch calls with repository methods for orphan cleanup and duplicate-stack fixing. |
src/Exceptionless.Core/Repositories/EventRepository.cs |
Adds bulk delete, stack reassignment, and composite aggregation helpers. |
src/Exceptionless.Core/Repositories/Interfaces/IEventRepository.cs |
Exposes new event repository cleanup/query APIs and composite cursor type. |
src/Exceptionless.Core/Repositories/StackRepository.cs |
Adds duplicate signature aggregation lookup. |
src/Exceptionless.Core/Repositories/Interfaces/IStackRepository.cs |
Exposes duplicate signature lookup API. |
tests/Exceptionless.Tests/Jobs/CleanupDataJobTests.cs |
Adds cleanup pagination and retention integration coverage. |
tests/Exceptionless.Tests/Jobs/CleanupOrphanedDataJobTests.cs |
Adds integration coverage for orphan cleanup and duplicate stack merging. |
tests/Exceptionless.Tests/Repositories/EventRepositoryTests.cs |
Adds coverage for distinct ids, stack reassignment, and bulk delete helpers. |
tests/Exceptionless.Tests/Repositories/StackRepositoryTests.cs |
Adds duplicate signature repository coverage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
7788c75 to
3c5880b
Compare
|
Merged origin/main and addressed the outstanding review feedback in 92fa434. Summary:
Verification:
All review threads are resolved. |
92fa434 to
281071e
Compare
|
Production-readiness pass complete on f6fcc4b.
Production-readiness verdict: READY. |
…ction-readiness-20260715
|
Production-readiness refresh complete on bd560b0.
Production-readiness verdict: READY. PR remains open and unmerged. |
|
Follow-up reviewer audit complete on c2bcdc6. Feedback ledger
RCA and current design proof
Thermo-nuclear review
Verification
Verdict: READY. No code changes were required in this follow-up audit; the PR remains open and unmerged. |
|
Final destructive-cleanup and query audit complete on Coverage and confidenceThis repository does not have 100% coverage, so I cannot honestly claim that no change can ever break anything. The exact-head hosted result is 78% line coverage (23,782/30,603) and 67% branch coverage (11,127/16,628); Core is 75%/67%. The authoritative isolated run is green: 2,742 .NET tests, 2,740 passed, 0 failed, and 2 intentional performance-test skips. All four new Deletion-safety findings and fixes
One residual limit is explicit: parent validation and event delete-by-query are separate cross-index operations. Supported ingestion writes the parent before the event, which satisfies the runtime invariant, but an out-of-contract event-first import or concurrent parent restore after the final check can still race cleanup. Eliminating that class absolutely would require an age grace period, two-pass quarantine, or cross-index coordination. Green tests cannot turn that non-atomic design into a mathematical guarantee. Query/performance verdictThe result is mixed, not uniformly “faster”:
CI proves behavior, not performance parity. A defensible performance gate would compare Verdict: merge-ready for the supported parent-before-event runtime invariant, with substantially stronger fail-closed behavior and exact-head integration proof. It is not 100%-covered, not an absolute cross-index no-loss guarantee, and not yet performance-benchmarked. |
|
Upstream follow-up for the strict MGET safety finding:
Exceptionless should retain its current fail-closed adapter until a released Foundatio.Repositories package contains #314. After upgrading, the intended cleanup is to replace the duplicated raw MGET implementation and internal interface-to-base-class cast with canonical I also merged the latest |
|
Final exact-head thermo and strict-MGET follow-up:
The MGET behavior was a real upstream Foundatio.Repositories gap: HTTP-200 per-item errors were logged and omitted, making them indistinguishable from missing documents. I filed issue #313 and opened PR #314. Both hosted build runs pass 841/841 tests with 0 failures/skips; CodeQL and CLA are green. The fix is opt-in/backward-compatible and adds no request round trip: strict mode changes only per-item response validation. Thermo verdict: no remaining blocking finding in this PR. Keep the local fail-closed adapter until #314 is released and the package is upgraded; then replace it with canonical |
…267-live-0700" This reverts commit 51465b9.
Summary
Comprehensive optimization of cleanup jobs: eliminate direct Elasticsearch client usage from jobs, fix critical bugs, preserve cleanup correctness under concurrent ingestion, and add full integration test coverage.
Critical Bug Fixes
@min_count:2→@min:2inGetDuplicateSignaturesAsyncThe
@min_countsyntax is silently ignored by Foundatio Parsers, causing the aggregation to return ALL signature hashes (not just duplicates). This would have causedFixDuplicateStacksAsyncto treat every stack as a duplicate on the next run.FixDuplicateStacksAsynconly ran one batch (regression from refactor)The original code looped until
GetDuplicateSignaturesAsyncreturned empty. The refactor broke this. Restored the loop with:ImmediateConsistency()onCountAsyncinsideGetDuplicateSignaturesAsync(one refresh per batch, matching the originalIndices.RefreshAsynccall pattern — NOT per item)ReassignStackAsyncdata-loss hazard on empty sequenceIf
sourceStackIdswas empty,PatchAllAsyncwould have no stack filter and would reassign ALL events to the target stack. Added materialization + early return guard.FixDuplicateStacksAsyncevent-first orderingEvent reassignment completes before a duplicate stack is finalized as a redirect tombstone. If event reassignment fails, duplicate stacks remain visible and no data is lost.
GetDistinctFieldValuesAsynccursor correctnessDistinct event identifiers use composite aggregation pagination with an explicit next-value cursor so callers reliably detect the end of pagination.
Architecture (eliminate raw ES client from jobs)
CleanupOrphanedDataJobto use repository methods exclusivelyGetDistinctFieldValuesAsyncusing composite aggregation (encapsulated in the repository — composite aggregation is not in Foundatio's DSL, so raw client use is justified and documented)RemoveAllByProjectIdsAsync/RemoveAllByOrganizationIdsAsyncandRemoveAllByStackIdsAsynctoIEventRepositoryReassignStackAsyncusing a parameterized Painless script and strict post-write verificationGetDuplicateSignaturesAsynctoIStackRepositoryConcurrency and lossless reconciliation
Stackpersistence so stale full-document saves cannot overwrite cleanup mergesindex_not_found_exception; other shard/index verification failures fail closedOther Fixes
OperationCanceledExceptionhandling before the intentionally broad per-signature catchis_deleted:falsefilters where repository soft-delete filtering applies:{Message}to error log format stringsMerged onto current main
Merged the latest
origin/mainwith a normal merge commit and kept the final diff limited to the intended cleanup, repository, serialization, and test files.Test Coverage
Coverage includes: