Skip to content

feat: retry catalog sync with exponential backoff to speed up cold-start recovery - #706

Closed
stefinie123 wants to merge 2 commits into
openchoreo:mainfrom
stefinie123:catalog-retry
Closed

feat: retry catalog sync with exponential backoff to speed up cold-start recovery#706
stefinie123 wants to merge 2 commits into
openchoreo:mainfrom
stefinie123:catalog-retry

Conversation

@stefinie123

@stefinie123 stefinie123 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Purpose

Resolves openchoreo/openchoreo#4324

After a cluster cold-start (k3d cluster stop/start), coreDNS and the Thunder IdP take ~1–2 min to become reachable. During that window the catalog sync can't get a service token, its first API call fails, and the whole periodic sync aborts —leaving the Backstage catalog empty/partial until the next scheduled run (default 5 min), long after the backend has recovered.

This PR makes the sync recover within ~30s of the backend becoming reachable instead of waiting a full sync interval.

Goals

Describe the solutions that this feature/fix will introduce to resolve the problems described above

Approach

  • Adaptive sync cadence. The scheduled task now fires at a short retryInterval (new config, default 30s) and the provider throttles internally: a healthy sync still runs only every schedule.frequency, but a failed run retries with
    capped exponential backoff — starting at retryInterval, doubling per consecutive failure, capped at frequency. It never stops retrying (so the catalog self-heals whenever the backend returns) and backs off during a sustained outage
    instead of hammering.
    • Token-acquisition retry. ClientCredentialsProvider retries transient token failures (network errors / 5xx) with backoff; 4xx (e.g. bad credentials) fail immediately.
    • Fail fast on missing token. The API client now throws when a service token can't be obtained instead of silently issuing unauthenticated requests that are guaranteed to 401 — so the run is cleanly marked failed and retried.
    • Config. Added openchoreo.schedule.retryInterval (default 30s) to the config schema and app-config; env-driven in production (OPENCHOREO_CATALOG_SYNC_RETRY_INTERVAL), matching how frequency is handled.

User stories

Summary of user stories addressed by this change>

Release note

Brief description of the new feature or bug fix as it will appear in the release notes

Documentation

Link(s) to product documentation that addresses the changes of this PR. If no doc impact, enter “N/A” plus brief explanation of why there’s no doc impact

Training

Link to the PR for changes to the training content in https://github.com/wso2/WSO2-Training, if applicable

Certification

Type “Sent” when you have provided new/updated certification questions, plus four answers for each question (correct answer highlighted in bold), based on this change. Certification questions/answers should be sent to certification@wso2.com and NOT pasted in this PR. If there is no impact on certification exams, type “N/A” and explain why.

Marketing

Link to drafts of marketing content that will describe and promote this feature, including product page changes, technical articles, blog posts, videos, etc., if applicable

Automation tests

  • Unit tests

    Code coverage information

  • Integration tests

    Details about the test cases and coverage

Security checks

Samples

Provide high-level details about the samples related to this feature

Related PRs

List any other related PRs

Migrations (if applicable)

Describe migration steps and platforms on which migration has been tested

Test environment

List all JDK versions, operating systems, databases, and browser/versions on which this feature/fix was tested

Learning

Describe the research phase and any blog posts, patterns, libraries, or add-ons you used to solve the problem.

Summary by CodeRabbit

  • New Features
    • Added adaptive catalog synchronization retries with capped exponential backoff after failures.
    • Added configurable retry intervals, defaulting to 30 seconds.
    • Added automatic retries for transient authentication token failures, including network errors and server errors.
  • Bug Fixes
    • Catalog synchronization now resumes sooner after backend recovery.
    • Authentication failures now stop immediately when a service token cannot be obtained, preventing unauthenticated requests.

Signed-off-by: Stefinie Fernando <minolispencer@gmail.com>
Signed-off-by: Stefinie Fernando <minolispencer@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Catalog synchronization now retries failed runs using capped exponential backoff. Client-credentials token acquisition retries transient failures, while catalog API authentication fails fast when token retrieval fails. Retry timing is configurable through openchoreo.schedule.retryInterval.

Changes

Catalog sync resilience

Layer / File(s) Summary
Token acquisition retry handling
packages/openchoreo-auth/src/ClientCredentialsProvider.ts, packages/openchoreo-auth/src/ClientCredentialsProvider.test.ts, plugins/catalog-backend-module-openchoreo/src/utils/openChoreoApiClient.ts
Token requests retry network and 5xx failures, avoid retries for 4xx responses, and propagate token failures instead of creating unauthenticated clients.
Adaptive catalog sync scheduling
plugins/catalog-backend-module-openchoreo/src/provider/OpenChoreoEntityProvider.ts, plugins/catalog-backend-module-openchoreo/src/provider/OpenChoreoEntityProvider.test.ts
Provider sync ticks use normal cadence after success and capped exponential backoff after failures, resetting retry state after recovery.
Retry configuration and scheduling wiring
plugins/openchoreo-common/config.d.ts, plugins/catalog-backend-module-openchoreo/src/module.ts, app-config.yaml, app-config.production.yaml
Adds the configurable retry interval, defaults it to 30 seconds, and uses it for scheduled task execution.
Release documentation
.changeset/catalog-sync-retry-backoff.md
Documents dependency updates and the catalog sync and token retry behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

  • #4324 — Implements exponential-backoff retries for failed catalog syncs instead of waiting for the full sync interval.

Suggested reviewers: sameerajayasoma

Sequence Diagram(s)

sequenceDiagram
  participant TaskRunner
  participant OpenChoreoEntityProvider
  participant OpenChoreoApiClient
  participant ClientCredentialsProvider
  participant CatalogAPI
  TaskRunner->>OpenChoreoEntityProvider: trigger sync tick
  OpenChoreoEntityProvider->>OpenChoreoApiClient: run catalog sync
  OpenChoreoApiClient->>ClientCredentialsProvider: obtain service token
  ClientCredentialsProvider->>CatalogAPI: request token
  CatalogAPI-->>ClientCredentialsProvider: token or transient failure
  ClientCredentialsProvider-->>OpenChoreoApiClient: token or final error
  OpenChoreoApiClient->>CatalogAPI: authenticated catalog request
  CatalogAPI-->>OpenChoreoEntityProvider: sync response
  OpenChoreoEntityProvider->>TaskRunner: schedule normal or backoff retry
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning Only Purpose and Approach are meaningfully filled; most required template sections remain empty or placeholder text. Add concrete content for Goals, User stories, Release note, Documentation, Automation tests, Security checks, and the remaining template sections.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The code adds retry/backoff, token retries, and fail-fast token handling, matching the recovery behavior requested by #4324.
Out of Scope Changes check ✅ Passed The changes are focused on catalog-sync retry recovery and related config/test updates, with no clearly unrelated code changes.
Title check ✅ Passed The title clearly summarizes the main change: retrying catalog sync with exponential backoff for faster recovery.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
plugins/catalog-backend-module-openchoreo/src/provider/OpenChoreoEntityProvider.ts (2)

454-460: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

run() bypasses the adaptive-cadence bookkeeping.

A manual/external run() that succeeds leaves consecutiveFailures and nextRetryAtMs untouched, so the provider keeps behaving as if it were still failing (and a success doesn't shift the normal-cadence window). Consider routing run() through the same state update, or documenting that it is intentionally out-of-band.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugins/catalog-backend-module-openchoreo/src/provider/OpenChoreoEntityProvider.ts`
around lines 454 - 460, Update OpenChoreoEntityProvider.run() to use the same
adaptive-cadence success bookkeeping as the normal execution path, resetting
consecutiveFailures and updating nextRetryAtMs after a successful runNew() call.
Preserve the existing connection validation and ensure failures continue to
propagate without being recorded as successes.

160-169: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Clamp the configured intervals to sane positives.

A misconfigured retryInterval: 0 (or a negative value) makes backoffMs 0 and turns every tick into a full sync; frequency: 0 does the same via the Math.min cap. A floor here is cheap insurance.

♻️ Proposed clamp
-    this.syncFrequencyMs =
-      (config.getOptionalNumber('openchoreo.schedule.frequency') ?? 300) * 1000;
+    this.syncFrequencyMs =
+      Math.max(
+        1,
+        config.getOptionalNumber('openchoreo.schedule.frequency') ?? 300,
+      ) * 1000;
@@
-    this.retryBaseMs =
-      (config.getOptionalNumber('openchoreo.schedule.retryInterval') ?? 30) *
-      1000;
+    this.retryBaseMs =
+      Math.max(
+        1,
+        config.getOptionalNumber('openchoreo.schedule.retryInterval') ?? 30,
+      ) * 1000;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugins/catalog-backend-module-openchoreo/src/provider/OpenChoreoEntityProvider.ts`
around lines 160 - 169, Clamp the configured values used to initialize
syncFrequencyMs and retryBaseMs to a sane positive minimum, after applying their
defaults and converting to milliseconds. Update the initialization near
OpenChoreoEntityProvider’s syncFrequencyMs and retryBaseMs assignments so zero
or negative schedule.frequency and schedule.retryInterval values cannot produce
zero-delay backoff or caps.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/openchoreo-auth/src/ClientCredentialsProvider.ts`:
- Around line 148-177: Update both expires_in fallback branches in the token
expiry logic surrounding decodeJwt and expiresAt to validate that
tokenResponse.expires_in is numeric before calculating the expiration. When it
is missing or non-numeric and no usable JWT exp exists, use a finite default
expiry so isTokenValid() can continue reusing the cached token instead of
producing NaN.

---

Nitpick comments:
In
`@plugins/catalog-backend-module-openchoreo/src/provider/OpenChoreoEntityProvider.ts`:
- Around line 454-460: Update OpenChoreoEntityProvider.run() to use the same
adaptive-cadence success bookkeeping as the normal execution path, resetting
consecutiveFailures and updating nextRetryAtMs after a successful runNew() call.
Preserve the existing connection validation and ensure failures continue to
propagate without being recorded as successes.
- Around line 160-169: Clamp the configured values used to initialize
syncFrequencyMs and retryBaseMs to a sane positive minimum, after applying their
defaults and converting to milliseconds. Update the initialization near
OpenChoreoEntityProvider’s syncFrequencyMs and retryBaseMs assignments so zero
or negative schedule.frequency and schedule.retryInterval values cannot produce
zero-delay backoff or caps.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3cba3b6d-73aa-44b4-9875-f8ceac568725

📥 Commits

Reviewing files that changed from the base of the PR and between bbc68ce and c39242c.

📒 Files selected for processing (10)
  • .changeset/catalog-sync-retry-backoff.md
  • app-config.production.yaml
  • app-config.yaml
  • packages/openchoreo-auth/src/ClientCredentialsProvider.test.ts
  • packages/openchoreo-auth/src/ClientCredentialsProvider.ts
  • plugins/catalog-backend-module-openchoreo/src/module.ts
  • plugins/catalog-backend-module-openchoreo/src/provider/OpenChoreoEntityProvider.test.ts
  • plugins/catalog-backend-module-openchoreo/src/provider/OpenChoreoEntityProvider.ts
  • plugins/catalog-backend-module-openchoreo/src/utils/openChoreoApiClient.ts
  • plugins/openchoreo-common/config.d.ts

Comment thread packages/openchoreo-auth/src/ClientCredentialsProvider.ts
@codecov

codecov Bot commented Jul 26, 2026

Copy link
Copy Markdown

@stefinie123 stefinie123 changed the title feat: implement exponential backoff for OpenChoreo catalog sync retries feat: retry catalog sync with exponential backoff to speed up cold-start recovery Jul 27, 2026
@stefinie123

Copy link
Copy Markdown
Contributor Author

Closing this as we decided not to move forward with this PR , since in memory state for keeping track of failed syncs will not function correctly on a multi replica scenario. Additionally we decided not do any custom implementations for failure scenario since backstage catalog sync has no in built retry fast on failure (since it is designed to be eventually consistent) , so building a custom retry + state mechanism would go against the framework's grain and feels like a hack.

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.

[Backstage] Retry failed catalog sync runs with exponential backoff instead of waiting a full sync interval

3 participants