Skip to content

fix(api): make delegated routing publish fire-and-forget#308

Merged
FSM1 merged 3 commits into
mainfrom
fix/ipns-publish-performance
Mar 21, 2026
Merged

fix(api): make delegated routing publish fire-and-forget#308
FSM1 merged 3 commits into
mainfrom
fix/ipns-publish-performance

Conversation

@FSM1

@FSM1 FSM1 commented Mar 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • DHT propagation via someguy takes 10-30s per IPNS record, blocking the API response
  • The DB record is already saved before the DHT publish, and resolveRecord() always prefers DB data, so awaiting DHT propagation is unnecessary
  • Changed await this.delegatedRouting.publish(...) to a detached promise chain
  • Batch operations (e.g. multi-folder delete) were taking minutes due to serialized 30s publishes — this should bring them down to seconds

Changes

  • apps/api/src/ipns/ipns.service.ts — fire-and-forget DHT publish with async metrics collection
  • apps/api/src/ipns/ipns.service.spec.ts — flush microtask queue in 3 tests that verify async metrics

Test plan

  • All 134 IPNS-related unit tests pass
  • Deploy to staging and verify batch delete completes in seconds
  • Verify IPNS resolve still returns correct data (DB-backed)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Delegated/IPNS publish now runs asynchronously so API responses return without waiting for delegated publish completion; failures are logged and recorded in metrics but no longer block the response.
  • Tests
    • Test timing adjusted to await microtask completion before asserting metrics to account for detached async behavior.

DHT propagation via someguy takes 10-30s per IPNS record. Since the DB
record is saved before the DHT publish and resolveRecord() always
prefers DB data, there is no need to block the API response. This was
causing batch operations (e.g. multi-folder delete) to take minutes.

Metrics are still collected via the detached promise chain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 2c05abce85b4
@coderabbitai

coderabbitai Bot commented Mar 21, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@FSM1 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 17 minutes and 3 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ba3ec3c3-2efb-4e1b-acaa-1cfa8dc5d9e0

📥 Commits

Reviewing files that changed from the base of the PR and between 783ba8c and 632ce03.

📒 Files selected for processing (1)
  • apps/api/src/ipns/ipns.service.spec.ts

Walkthrough

publishRecord() now invokes delegated routing publish as fire-and-forget (no await); delegated publish errors are logged and recorded asynchronously. Tests were updated to flush the Node.js microtask queue so detached callbacks run before assertions.

Changes

Cohort / File(s) Summary
IPNS Service Implementation
apps/api/src/ipns/ipns.service.ts
Replaced awaited delegatedRouting.publish() try/catch/finally with a detached .catch().then() chain; delegated publish no longer blocks API response; metrics observation moved into the detached chain and failures are logged.
IPNS Service Tests
apps/api/src/ipns/ipns.service.spec.ts
Added await new Promise(process.nextTick) microtask flushes in publish success and several error tests to let detached delegated-routing promise callbacks execute before metrics assertions.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant IpnsService
    participant DelegatedRouting
    participant Metrics
    Client->>IpnsService: publishRecord(request)
    IpnsService->>IpnsService: perform primary publish (blocking)
    IpnsService-->>Client: respond (primary result)
    IpnsService->>DelegatedRouting: publish(...) (fire-and-forget)
    DelegatedRouting-->>IpnsService: resolve / reject (async)
    IpnsService->>Metrics: record delegated publish outcome (inside detached chain)
    Note right of Metrics: Errors in metrics observation are logged
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: converting delegated routing publish from an awaited operation to a fire-and-forget operation, which directly addresses the performance issue identified in the PR objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ipns-publish-performance

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 and usage tips.

@codecov

codecov Bot commented Mar 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 49.82%. Comparing base (ca356bb) to head (632ce03).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
apps/api/src/ipns/ipns.service.ts 88.88% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #308      +/-   ##
==========================================
- Coverage   49.83%   49.82%   -0.02%     
==========================================
  Files         114      114              
  Lines        9126     9126              
  Branches      699      701       +2     
==========================================
- Hits         4548     4547       -1     
  Misses       4402     4402              
- Partials      176      177       +1     
Flag Coverage Δ
api 81.81% <88.88%> (-0.03%) ⬇️
api-client 81.81% <88.88%> (-0.03%) ⬇️
core 81.81% <88.88%> (-0.03%) ⬇️
crypto 81.81% <88.88%> (-0.03%) ⬇️
sdk 81.81% <88.88%> (-0.03%) ⬇️
sdk-core 81.81% <88.88%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
apps/api/src/ipns/ipns.service.ts 88.46% <88.88%> (-0.65%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@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.

🧹 Nitpick comments (1)
apps/api/src/ipns/ipns.service.ts (1)

81-95: Consider adding a terminal .catch() for defensive error handling.

The fire-and-forget pattern correctly handles delegated routing failures via .catch(), but any exception thrown inside the .then() callback (e.g., if metricsService.ipnsPublishDuration.observe() fails) would surface as an unhandled promise rejection.

While unlikely in practice, adding a terminal .catch() ensures no silent failures in the metrics path:

🛡️ Proposed hardening
     this.delegatedRouting
       .publish(dto.ipnsName, recordBytes)
       .catch((error) => {
         this.logger.warn(
           `Delegated routing publish failed for ${dto.ipnsName}, DB record saved: ${error instanceof Error ? error.message : String(error)}`
         );
         return 'error' as const;
       })
       .then((outcome) => {
         const publishElapsed = Number(process.hrtime.bigint() - publishStart) / 1e9;
         this.metricsService.ipnsPublishDuration.observe(
           { outcome: outcome === 'error' ? 'error' : 'success' },
           publishElapsed
         );
-      });
+      })
+      .catch((err) => {
+        this.logger.warn(`Failed to record IPNS publish metrics: ${err instanceof Error ? err.message : String(err)}`);
+      });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/ipns/ipns.service.ts` around lines 81 - 95, The promise chain
starting at delegatedRouting.publish(dto.ipnsName, recordBytes) needs a terminal
.catch to handle exceptions thrown inside the .then() (e.g., failures in
metricsService.ipnsPublishDuration.observe), so append a final .catch that logs
the error (use this.logger.error or this.logger.warn) and, if appropriate, marks
the metrics outcome as 'error' using dto.ipnsName and publishStart context;
ensure you reference delegatedRouting.publish, the .then callback that computes
publishElapsed from publishStart, and metricsService.ipnsPublishDuration.observe
when adding the defensive catch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/api/src/ipns/ipns.service.ts`:
- Around line 81-95: The promise chain starting at
delegatedRouting.publish(dto.ipnsName, recordBytes) needs a terminal .catch to
handle exceptions thrown inside the .then() (e.g., failures in
metricsService.ipnsPublishDuration.observe), so append a final .catch that logs
the error (use this.logger.error or this.logger.warn) and, if appropriate, marks
the metrics outcome as 'error' using dto.ipnsName and publishStart context;
ensure you reference delegatedRouting.publish, the .then callback that computes
publishElapsed from publishStart, and metricsService.ipnsPublishDuration.observe
when adding the defensive catch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0677da66-41f0-4ec1-9622-edd0c754a0fe

📥 Commits

Reviewing files that changed from the base of the PR and between ca356bb and 0da0c19.

📒 Files selected for processing (2)
  • apps/api/src/ipns/ipns.service.spec.ts
  • apps/api/src/ipns/ipns.service.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the API’s IPNS publish path to avoid blocking responses on slow delegated routing/DHT propagation by converting delegated routing publish into a fire-and-forget operation, while keeping metrics collection asynchronous.

Changes:

  • Make DelegatedRoutingClient.publish(...) fire-and-forget and record ipnsPublishDuration metrics asynchronously.
  • Update unit tests to flush the queue so detached metrics callbacks run before assertions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
apps/api/src/ipns/ipns.service.ts Converts delegated routing publish to a detached promise chain and keeps publish duration metrics.
apps/api/src/ipns/ipns.service.spec.ts Adds microtask/nextTick flushes in tests that assert async metrics behavior.

Comment thread apps/api/src/ipns/ipns.service.ts Outdated
Comment thread apps/api/src/ipns/ipns.service.ts
FSM1 and others added 2 commits March 21, 2026 19:16
- Fix comment saying "finally block" when code uses catch+then chain
- Add trailing .catch() to prevent unhandled rejection if observe() throws

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 4f5e70eaac60
…get chain

Covers the trailing .catch() that handles errors from
ipnsPublishDuration.observe() throwing in the detached promise chain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: fc32db80737c
@FSM1 FSM1 merged commit e49973a into main Mar 21, 2026
14 checks passed
@FSM1 FSM1 deleted the fix/ipns-publish-performance branch March 21, 2026 18:30
This was referenced Mar 22, 2026
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.

2 participants