sqlite_stat1 records only the average row count for each effect
status. When most effects are complete, SQLite estimated that
status = 'processing' matched most of the effects table. It then read
every effect_recoveries row through the instance index on each effect
poll. On generated data with 100,000 completed effects and 10,000
recovery rows, the query took 6.3 ms.
On SQLite the status test now carries likelihood(..., 0.000001), which
overrides the statistics estimate for that term. The plan searches the
effects poll index first and takes 0.015 ms on the same data. The
PostgreSQL and MySQL SQL does not change. Ruby 0.16.1 has the same fix.
Ruby 0.16.1 also fixes the join order of its claimed-message scan.
JavaScript has no such scan, and SQLite already starts
recoverExpiredClaims from the claimed messages, so that fix needs no
change here.
Why
The recovery candidate query runs at the start of every
claimEffectand every stale-process cleanup. On SQLite it chose a bad plan when most effects were complete.sqlite_stat1records only the average row count for each effectstatus. When most effects are complete, SQLite estimated thatstatus = 'processing'matched most of the effects table. It then read every recovery row through the instance index:On generated data with 100,000 completed effects and 10,000 recovery rows, the query took 6.3 ms.
Fix
On SQLite the status test now carries
likelihood(..., 0.000001). SQLite uses that value in place of thesqlite_stat1estimate for the term:The same query took 0.015 ms. The PostgreSQL and MySQL SQL does not change.
unlikely()is too weak at 500,000 effects, andCROSS JOINalone scans the whole effects table when every effect has one status. The Ruby PR has that comparison.Ruby 0.16.1 has the same fix in cardmagic/solid-objects-ruby#82. That PR also fixes the join order of the Ruby claimed-message scan. JavaScript has no such scan, and SQLite already starts
recoverExpiredClaimsfrom the claimed messages, so that fix needs no change here.Tests
The new SQLite plan test in
test/polling-queries.test.tsfailed on the old code:It also asserts the cause: the poll index statistics give 3,000 rows for each status. The test records the real query that
claimEffectsends, with its parameters, and runsEXPLAIN QUERY PLANon it. The existingtest/effect-recovery.test.tscases cover which candidates come back on every adapter.Validation
pnpm run format:checkpnpm run checkpnpm test(SQLite)SOLID_OBJECTS_DATABASE_URL=postgresql://... pnpm run test:postgresql(17)SOLID_OBJECTS_DATABASE_URL=mysql://... pnpm run test:mysql(8.4)I did not run
test:coverage,test:cloudflare, or the browser suites. The Cloudflare runtime does not use this query.Docs
CHANGELOG.md: an entry under Unreleased. The package version stays 0.16.0.docs/parity.md: the effect recovery section records that both runtimes mark the status test withlikelihood()on SQLite. The row status stays Native.