Skip to content

Thread-list reads still run synchronously on the event loop after #1204 #1207

Open

Description

@SawyerHood

⚠ Agent-generated: An AI agent produced these measurements. Please verify
details before you act on them.

Summary

The thread-list route still reads SQLite synchronously on the event loop. After
#1204, the cost is bounded but not zero.

PR #1198 moved these reads to a worker thread. We closed that PR because #1204
removed the reason for it. This issue keeps the measurements and the option.

Why we closed #1198

PR #1198 answered an 11.2 second event-loop stall. #1204 showed that the stall
came from listLatestGoalEventRowsByThreadIds, not from the thread-list query,
and cut it from 10.5s to 0.10s cold. A worker thread moves a slow read off the
loop; it does not make the read fast. #1204 makes it fast.

#1198 also carried a large amount of permanent machinery: a worker thread, a
32-slot queue cap, request deadlines, abort handling, worker restart, safe
shutdown, and a new 503 contract. Three automated review rounds and two
adversarial review passes found six defects in it, including one bad database
row disabling all thread lists until restart, and every client abort writing an
error log and a 500. The maintenance cost looked high against a residual that
is no longer catastrophic.

What remains

Measured on a rebuilt 1.45GB database with 26K threads, with read IOPS capped at
1000 in a cgroup:

  • About 1,700 to 2,000 scattered page reads to fetch the returned rows.
  • About 2 seconds cold for a full 26K-thread list.
  • About 50 to 170 ms warm, which is per-row CPU.

This cost is inherent. It comes from row scatter in a threads table
interleaved with a large events table. The investigation tested a 24-column
covering index (the planner does not use it reliably) and a full VACUUM with
an ideal sequential scan (still 0.85s at 1000 IOPS). No index removes it.

Removing the GROUP BY and ORDER BY temporary B-trees improved cold time by
only about 4 percent.

When to revisit

Reconsider a worker thread, or another way off the event loop, if any of these
becomes true:

Related

Not covered here

The retryable field on API errors is produced today on main (for example
apps/server/src/services/threads/thread-create.ts:146 and several host-daemon
contract errors) and no client code reads it. That is a separate pre-existing
bug and needs its own issue.

Activity

  1. added theissue type on Aug 8, 2026
  2. added a commit that references this issue on Aug 8, 2026
  3. amadad commented on Aug 12, 2026

    @amadad
    Contributor

    The revisit condition is now met on a production 0.37.0 install, but on the timeline route rather than thread-list.

    With a 1.66 GiB database, one latest-page timeline request blocked the event loop for 3,578 ms; 3,462 ms was the synchronous event query for 1,212 rows / 1.74 MiB. Earlier warm requests were commonly 270–330 ms.

    This suggests any worker/read-isolation mechanism should be owned once by the DB read seam and serve both thread-list and timeline callers, rather than adding a timeline-local workaround. I also added the full measurements to #1131.

  4. added a commit that references this issue on Aug 14, 2026
  5. added
    hostHost daemon, process lifecycle, memory, event loop
    on Aug 18, 2026
  6. SawyerHood commented on Sep 9, 2026

    @SawyerHood
    CollaboratorAuthor

    Tracked as a sub-issue of #1131. The parent now covers the shared scope and links this implementation. This issue retains its distinct evidence and acceptance criteria and remains open until that work is complete.

    AGENT GENERATED

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    hostHost daemon, process lifecycle, memory, event loopperf

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions