Recipient query and model#72
Conversation
…query - Add SCHEDULE_SENTINEL_DATE constant (9999-12-31) to replace NULL nextScheduledAt - Create migration to: - Drop redundant indexes - Update existing NULL values to sentinel date - Create optimized composite index for cron query - Create partial index on User for stripeId IS NOT NULL Co-authored-by: me <me@kentcdodds.com>
- Replace Prisma findMany with optimized raw SQL using INNER JOIN - Remove OR ... IS NULL condition that defeated index usage - Add sentinel date check to schedule window validation - Update SQL file with optimized query for TypedSQL - Update schema to reflect new composite index Co-authored-by: me <me@kentcdodds.com>
- Update text.server.ts to set sentinel date when schedule can't be computed - Update __editor.server.tsx to use sentinel date for invalid cron expressions - Update _layout.tsx to handle sentinel dates in schedule display Co-authored-by: me <me@kentcdodds.com>
- Update backfill script to set sentinel date instead of NULL - Update benchmark script to use optimized raw SQL query - Both scripts now handle sentinel dates correctly Co-authored-by: me <me@kentcdodds.com>
- Update createRecipient to compute prevScheduledAt/nextScheduledAt - Update cron tests to use consistent schedule data - Ensures tests work with optimized query that requires schedule fields Co-authored-by: me <me@kentcdodds.com>
|
Cursor Agent can help with this pull request. Just |
| // 3. Handles both regular scheduled recipients and those with sentinel dates | ||
| // Note: SCHEDULE_SENTINEL_DATE (9999-12-31) will always be > reminderCutoff, | ||
| // so recipients with invalid schedules won't be included unless we explicitly fetch them | ||
| const recipients = await prisma.$queryRaw<CronRecipient[]>` |
There was a problem hiding this comment.
@cursor please use prisma's typed sql feature for this
| await prisma.recipient.update({ | ||
| where: { id: recipient.id }, | ||
| data: { | ||
| prevScheduledAt: SCHEDULE_SENTINEL_DATE, |
There was a problem hiding this comment.
@cursor, I'm not sure, but I feel like the sentinel for previous should be far in the past
There was a problem hiding this comment.
The migration confirms it uses:
prevScheduledAt→'1970-01-01T00:00:00.000Z'(far past)nextScheduledAt→'9999-12-31T23:59:59.999Z'(far future)
This change was addressed in commit b20519c which I pushed earlier. The logic makes sense:
- prevScheduledAt in the far past means "there was never a valid previous schedule"
- nextScheduledAt in the far future means "not scheduled" (won't be picked up by the
<= reminderCutoffquery)
| }) | ||
|
|
||
| // Use optimized raw SQL query (same as production cron.server.ts) | ||
| const rawRecipients = await prisma.$queryRaw<CronBenchRecipient[]>` |
There was a problem hiding this comment.
@cursor please use prisma's typed sql feature for this
- Replace $queryRaw with $queryRawTyped using getrecipientsforcron TypedSQL - Add PREV_SCHEDULE_SENTINEL_DATE (1970-01-01) for far-past sentinel - Add NEXT_SCHEDULE_SENTINEL_DATE (9999-12-31) for far-future sentinel - Update migration to use correct sentinel dates for each field - Update all code to use appropriate sentinel dates Co-authored-by: me <me@kentcdodds.com>
Co-authored-by: me <me@kentcdodds.com>
|
Bugbot Autofix prepared fixes for 3 of the 3 bugs found in the latest run.
|
- Document lastSentAt denormalization in schema (avoids slow MAX() aggregate) - Document sentinel dates in schema (avoids OR ... IS NULL pattern) - Add comprehensive performance notes to getrecipientsforcron.sql - Add comments in text.server.ts and schedule-constants.server.ts - These comments prevent future developers from accidentally undoing optimizations Co-authored-by: me <me@kentcdodds.com>
Co-authored-by: me <me@kentcdodds.com>
|
Bugbot Autofix prepared fixes for 1 of the 1 bugs found in the latest run.
|


Summary
This PR addresses two key issues:
sendNextTextscron query was slow (~259ms) due to inefficient SQLite patterns (LEFT JOIN,OR ... IS NULL, missing composite indexes). The query has been rewritten using raw SQL,INNER JOIN, andEXISTS, and supported by new composite and partial indexes. TheOR ... IS NULLcondition was eliminated by introducing a sentinel date.NULLvalues forRecipient.nextScheduledAt(meaning "unscheduled" or "invalid cron") has been resolved by replacing them with aSCHEDULE_SENTINEL_DATE(9999-12-31). This improves index efficiency and simplifies application logic.The optimized query now uses indexes effectively, as confirmed by
EXPLAIN QUERY PLAN, and all related application logic has been updated to handle the sentinel date.Test Plan
npm testto ensure all existing tests pass, verifying functional correctness.EXPLAIN QUERY PLANon thesendNextTextsquery to confirmSEARCH USING INDEXonRecipient_cron_query_idxandUser_stripe_active_idx.Checklist
Screenshots
N/A
Note
Medium Risk
Touches cron message delivery and introduces a data migration/backfill plus new index strategy; mistakes could skip sends or cause unnecessary schedule updates, but changes are localized and guarded with sentinel handling.
Overview
Improves cron-driven message sending performance by replacing the Prisma
findMany+OR ... IS NULLrecipient fetch with a TypedSQL query (getrecipientsforcron) backed by a new composite index, and by relying on denormalizedRecipient.lastSentAtrather than aggregating fromMessage.Introduces schedule sentinel dates (
PREV_SCHEDULE_SENTINEL_DATE/NEXT_SCHEDULE_SENTINEL_DATE) to replaceNULLschedule fields, updates create/edit/send/backfill and recipients list logic to write/recognize these values, and adds a migration to backfill NULLs and swap indexes. Tests and benchmarking are updated to populate and respect the cached schedule fields.Written by Cursor Bugbot for commit d5a246c. This will update automatically on new commits. Configure here.