Fix broadcast polling query plans - #60
Merged
Merged
Conversation
The pending and stale predicates previously fed one OR query, forcing eligible rows through a sort before LIMIT 1. Lock one ordered candidate from each state and choose the earlier row so each probe follows the existing polling index without changing delivery order or claimant safety.
Greptile SummaryThe PR replaces the combined broadcast polling query with separate pending and stale-processing probes while preserving eligibility and global delivery ordering.
Confidence Score: 5/5The PR appears safe to merge with no concrete blocking or independently actionable non-blocking issue identified. The split probes preserve the existing eligibility predicates, compare non-null ordering fields consistently, retain transactional claim locking, and are covered by ordering and concurrency tests. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Begin claim transaction] --> B[Probe earliest pending broadcast]
B --> C[Probe earliest stale processing broadcast]
C --> D{Any candidate?}
D -->|No| E[Return without work]
D -->|Yes| F[Choose minimum available_at and id]
F --> G[Update selected row to processing]
G --> H[Commit transaction]
H --> I[Deliver outside claim transaction]
Reviews (1): Last reviewed commit: "fix: split broadcast polling queries" | Re-trigger Greptile |
Prepare the patch release for the broadcast polling query fix and record that existing installations need no migration or new index.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Split broadcast claiming into separate pending-delivery and stale-processing
recovery probes. Each probe can follow the existing
(status, available_at, id)polling index. Both candidates are locked in thesame short transaction with
FOR UPDATE SKIP LOCKED, and the executor choosesthe earlier
(available_at, id)candidate, preserving the previous globalordering.
No schema migration or new index is included. The existing polling indexes are
the correct indexes for effects, reminders, and both broadcast probes. Another
index would add write cost without improving the measured plans. Existing
installations receive the query change by upgrading the gem, and schema coverage
now protects those canonical indexes.
Production evidence
LEADx Web App on MySQL reported these Solid Objects polling spans on 2026-09-03
around 06:00 UTC:
SolidObjects::Effect LoadSolidObjects::Broadcast LoadSolidObjects::Reminder LoadThe broadcast query combined pending delivery and stale recovery with an
OR:The effect and reminder queries already led with their matching ordered
indexes. The similar production durations across three different tables are
not explained by those two query plans. They are consistent with a shared
database wait or contention event, but the supplied spans do not identify the
specific production wait. This change therefore fixes the independently
reproduced broadcast planner defect and does not pretend an extra effect or
reminder index would cure shared database contention.
Query plans
I loaded 50,000 production-shaped rows per table and ran
EXPLAIN ANALYZEonMySQL 8.4 and PostgreSQL 18. SQLite 3.53.2 was checked with
EXPLAIN QUERY PLAN.Before
Effects and reminders used their intended ordered range scans:
idx_so_effects_poll, 0.639 msidx_so_reminders_due, 0.012 msThe broadcast
ORgathered every eligible row before applying the limit:Sort ... limit input to 1;4.1 ms.
BitmapOrand bitmap heap scan returned 2,000 rows, followed byquicksort; 1.101 ms and 1,047 shared-buffer hits.
MULTI-INDEX ORfollowed byUSE TEMP B-TREE FOR ORDER BY.After
The executor issues two ordered probes:
idx_so_broadcasts_poll, return one row directly, and take0.091 ms plus 0.037 ms.
and take 0.069 ms plus 0.020 ms.
idx_so_broadcasts_poll; neither uses a temporary sort.Correctness and compatibility
(available_at, id)ordering across the two states.FOR UPDATE SKIP LOCKEDon PostgreSQL, mysql2, and Trilogy.while the first delivery is blocked.
pending work.
ORpoll and schema coveragefor all three canonical polling indexes.
TDD evidence
Red, before the implementation:
Green, after the implementation:
Validation