Skip to content

Fix job list performance and SQLite pagination - #1374

Open
bgentry wants to merge 2 commits into
masterfrom
bg/job-list-finalized-index
Open

Fix job list performance and SQLite pagination#1374
bgentry wants to merge 2 commits into
masterfrom
bg/job-list-finalized-index

Conversation

@bgentry

@bgentry bgentry commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Listing completed jobs can scan the entire job table even though an index exists for this query. Earlier work explicitly aimed to use that index (#304). The finalized_at IS NULL / IS NOT NULL conditions may have been lost along the way, but we haven't found a removal commit.

When filtering by one state, use state = ... instead of state = ANY(...). For completed, cancelled, or discarded jobs sorted by finalized time, also add finalized_at IS NOT NULL. Both changes are needed to make the measured query fast. River UI benefits without any changes to its calls. This optimization skips queries with custom SQL, multiple states, and job deletion.

A separate commit fixes SQLite pagination skipping or repeating jobs. Format cursor timestamps the same way as stored timestamps so comparisons work correctly.

Listing 100 completed jobs on a local PostgreSQL 17.11 database with 888,000 jobs:

Prepared plan Before After
Custom 102.426 ms 0.073 ms
Generic 171.081 ms 0.062 ms

The fixed query uses the existing index and reads 103 rows instead of scanning 888,000. These are individual measurements, not averages.

@bgentry
bgentry force-pushed the bg/job-list-finalized-index branch 7 times, most recently from e8e6290 to 05cde7f Compare September 10, 2026 01:14
Listing one finalized state by time can scan and sort the entire job
table. PostgreSQL cannot use the partial finalized-time index without
an explicit non-null predicate, and singleton ANY prevents it from
using the index's time ordering.

Build state equality with the list API's existing condition mechanism
when custom SQL cannot depend on the existing array argument. Add
`finalized_at IS NOT NULL` only for one known finalized state ordered
by finalized time. Keep the shared builder and drivers unchanged.
Preserve custom SQL, multi-state filters, and deletion queries. Build
cursor predicates locally so conversion preserves the caller's conditions.

Cover finalized states, both ordering directions, tied timestamps,
PostgreSQL pagination, and combined filters across supported drivers.
Check custom OR expressions, contradictory conditions, argument binding,
and unchanged delete-many query generation.
@bgentry
bgentry force-pushed the bg/job-list-finalized-index branch from 05cde7f to e50368a Compare September 10, 2026 01:19
SQLite stores job timestamps as formatted text, while JobList passes
cursor values directly to database/sql. Different encodings can skip
or repeat jobs when a page boundary shares a timestamp.

Format `time.Time` and `*time.Time` list arguments with SQLite's existing
timestamp helpers. Copy the argument map so conversion leaves reusable
parameters intact, including custom conditions and nullable values.

Enable finalized-job pagination coverage for SQLite, libSQL, and Turso.
Cover scheduled-job pagination, millisecond precision, time zones,
nullable arguments, and repeated use of driver parameters.
@bgentry bgentry changed the title Speed up single-state finalized job lists Fix job list performance and SQLite pagination Sep 10, 2026
@bgentry
bgentry marked this pull request as ready for review September 10, 2026 01:28
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.

1 participant