Skip to content

Deliver scheduled cluster messages at their deadline instead of the next poll#6452

Open
sbking wants to merge 4 commits into
Effect-TS:mainfrom
sbking:feat/cluster-deliver-at-rebuild
Open

Deliver scheduled cluster messages at their deadline instead of the next poll#6452
sbking wants to merge 4 commits into
Effect-TS:mainfrom
sbking:feat/cluster-deliver-at-rebuild

Conversation

@sbking

@sbking sbking commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Messages saved with a future deliverAt are only discovered by the periodic storage poll, so a scheduled message can be delivered up to a full poll interval after its deadline.

  • add a required MessageStorage.nextDeliverAt operation that returns the earliest future scheduled delivery time for a set of shards, with memory and SQL driver implementations
  • after every storage read, Sharding re-arms a single replaceable timer for that deadline; when it fires, it runs the existing storage read path
  • the wake affects promptness only: if nextDeliverAt fails, the error is logged at debug level and delivery falls back to the existing poll
  • use explicit null checks in the memory driver so due-message selection and nextDeliverAt agree when deliverAt is zero

PostgreSQL and SQLite answer the query with MIN(deliver_at) over a partial index on (shard_id, deliver_at) filtered to unprocessed scheduled rows. MySQL and SQL Server have no partial indexes, so they seek per shard (correlated scalar subquery and CROSS APPLY ... TOP 1) over a plain (shard_id, processed, deliver_at, last_read) index, which stays flat as processed rows accumulate. Migration 0003_deliver_at_index creates the index idempotently on all four dialects. #6441 independently adds a 0003 migration; whichever PR rebases second will renumber its migration.

Testing

  • new tests: a message scheduled before a runner starts is delivered at its deadline instead of the next poll; a failing nextDeliverAt degrades to poll-cadence delivery with the read loop still usable; deliverAt at epoch zero across the due boundary
  • pnpm vitest run packages/effect/test/cluster/MessageStorage.test.ts packages/effect/test/cluster/Sharding.test.ts packages/effect/test/cluster/Entity.test.ts (42/42)
  • the shared SQL storage suite passes against PostgreSQL, MySQL, and SQLite containers (pnpm vitest run packages/platform-node/test/cluster/SqlMessageStorage.test.ts); the SQL Server leg passes on a local branch combining this change with Support SQL Server in cluster SqlMessageStorage #6441's container fixture, with this migration renumbered to 0004 there
  • pnpm check
  • pnpm lint

Benchmark protocol, reproduction harnesses, and raw results: https://gist.github.com/sbking/0a56b3f94ca518f4e50308f045e75a45

Summary by CodeRabbit

  • New Features
    • Added nextDeliverAt to query the earliest upcoming scheduled delivery time for pending messages across selected shards.
    • Scheduled messages now wake specifically at their deliverAt deadline, including messages already persisted before startup.
    • Implemented across in-memory and SQL storage backends (with added SQL support).
  • Bug Fixes
    • Corrected handling of immediate/epoch-zero scheduling and edge cases where scheduled lookups fail (fallback to polling).
  • Performance
    • Added a SQL migration to introduce a deliver_at-oriented index for faster scheduling queries.
  • Tests
    • Expanded cluster and storage test coverage for scheduled delivery behavior.

@github-project-automation github-project-automation Bot moved this to Discussion Ongoing in PR Backlog Jul 17, 2026
@changeset-bot

changeset-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: bb0e880

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 27 packages
Name Type
effect Patch
@effect/opentelemetry Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-node-shared Patch
@effect/platform-node Patch
@effect/vitest Patch
@effect/ai-anthropic Patch
@effect/ai-openai-compat Patch
@effect/ai-openai Patch
@effect/ai-openrouter Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/openapi-generator Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

The NVARCHAR literals cause SQL Server to convert the indexed VARCHAR `shard_id` column, preventing the per-shard index seek.
@IMax153 IMax153 added the 4.0 label Jul 17, 2026
@IMax153

IMax153 commented Jul 17, 2026

Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f34763c0-da91-4571-abf3-146a302dbfaa

📥 Commits

Reviewing files that changed from the base of the PR and between 461d014 and bb0e880.

📒 Files selected for processing (4)
  • packages/effect/src/unstable/cluster/Sharding.ts
  • packages/effect/src/unstable/cluster/SqlMessageStorage.ts
  • packages/effect/test/cluster/Sharding.test.ts
  • packages/platform-node/test/cluster/SqlMessageStorage.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/effect/src/unstable/cluster/SqlMessageStorage.ts
  • packages/effect/test/cluster/Sharding.test.ts
  • packages/effect/src/unstable/cluster/Sharding.ts

📝 Walkthrough

Walkthrough

Adds MessageStorage.nextDeliverAt to memory and SQL storage, introduces SQL indexes for delivery-time queries, and updates sharding storage reads to wake at scheduled delivery times. Tests cover memory, SQL, deadline handling, persisted messages, and polling fallback.

Changes

Scheduled delivery discovery

Layer / File(s) Summary
Storage contract and memory implementation
packages/effect/src/unstable/cluster/MessageStorage.ts, packages/effect/test/cluster/MessageStorage.test.ts, .changeset/*
Adds nextDeliverAt to storage contracts and implementations, handles epoch-zero timestamps, filters shards and completed requests, and adds memory coverage.
SQL delivery query and migration
packages/effect/src/unstable/cluster/SqlMessageStorage.ts, packages/platform-node/test/cluster/*
Adds dialect-specific minimum-delivery queries, delivery-time indexes, and SQL tests for shard filtering and completed requests.
Storage inbox wake-up scheduling
packages/effect/src/unstable/cluster/Sharding.ts, packages/effect/test/cluster/Sharding.test.ts, packages/effect/test/cluster/TestEntity.ts
Re-derives storage wake-ups from scheduled delivery times, supports earlier deadlines and persisted messages, and falls back to polling on lookup failure.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant StorageInbox
  participant MessageStorage
  participant StorageWakeHandle
  participant StorageReadLatch
  StorageInbox->>MessageStorage: Query nextDeliverAt(acquiredShards)
  MessageStorage-->>StorageInbox: Return next scheduled timestamp
  StorageInbox->>StorageWakeHandle: Schedule delay
  StorageWakeHandle->>StorageReadLatch: Re-open at deadline
  StorageReadLatch-->>StorageInbox: Resume storage reads
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: scheduled cluster messages now deliver at their deadline instead of waiting for the next poll.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

🤖 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/effect/src/unstable/cluster/SqlMessageStorage.ts`:
- Around line 406-445: Update getNextDeliverAt so shard IDs are passed as bound
SQL parameters rather than interpolated through wrapString in the mysql, mssql,
and orElse branches. Preserve each dialect’s existing query shape while
replacing the UNION ALL, VALUES, and IN input construction with parameterized
SQL expressions that safely handle embedded quotes.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0954d953-e1d6-4d68-96f1-99fe7ec9dd98

📥 Commits

Reviewing files that changed from the base of the PR and between 212493b and 461d014.

📒 Files selected for processing (9)
  • .changeset/cluster-message-storage-next-deliver-at.md
  • packages/effect/src/unstable/cluster/MessageStorage.ts
  • packages/effect/src/unstable/cluster/Sharding.ts
  • packages/effect/src/unstable/cluster/SqlMessageStorage.ts
  • packages/effect/test/cluster/MessageStorage.test.ts
  • packages/effect/test/cluster/Sharding.test.ts
  • packages/effect/test/cluster/TestEntity.ts
  • packages/platform-node/test/cluster/MessageStorageTest.ts
  • packages/platform-node/test/cluster/SqlMessageStorage.test.ts

Comment thread packages/effect/src/unstable/cluster/SqlMessageStorage.ts
Shard identifiers now reach the nextDeliverAt queries as bound parameters
in all three dialect branches, with SQL Server keeping its VARCHAR cast.
The storage read loop discovers the next scheduled deadline before reading
due messages, so a deadline crossing between the two reads arms a wake
instead of waiting for the next poll interval. Regression tests cover a
quote-bearing shard group and a deadline that lands between the reads.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Discussion Ongoing

Development

Successfully merging this pull request may close these issues.

2 participants