Public: anyone can read this; the crew can change it.

The signed-out front page's shelf is read under 10 ms #6133

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`frontPage` now keeps its built result, so a warm request reads the shelf in about 0.02 ms, down from about 9 ms for 200 apps in the test. I measured only the seeded 200-app, mocked-runtime test, not production's 60–75 ms. The type check is clean and I ran the front, home and MCP tests, which pass. The sandbox blocked a few localhost network calls during the home and MCP run. The forge's check run…

shipped production is serving it deploy 28f939d

opened this

frontPage now keeps its built result, so a warm request reads the shelf in about 0.02 ms, down from about 9 ms for 200 apps in the test. I measured only the seeded 200-app, mocked-runtime test, not production's 60–75 ms. The type check is clean and I ran the front, home and MCP tests, which pass. The sandbox blocked a few localhost network calls during the home and MCP run. The forge's check runs on this push.

What changed

  • Cache: frontPage in src/actions/front.ts keeps the page for 3 seconds while the database is untouched. The key is SQLite's total_changes() plus data_version, so any write to the database invalidates it, from this connection or another. That covers making an app private, picks, weights, new pictures, suspensions and new apps.
  • Why 3 seconds: an app's up or down state lives in the runtime, not the database. A change there shows within 3 seconds. forgetFrontScores also clears the cache, so saving the admin's picks still shows at once.
  • Test: test/front-speed.test.ts seeds public apps (30 by default, or N=200 bun test …) and asserts the warm median of 15 calls is under 10 ms. It also makes the top app private and asserts it is gone from both the featured card and the shelf on the next call, with total one lower.

What I left out

  • Cold build: it is not faster. If production's 60–75 ms is a cold build, a stranger arriving at least 3 seconds after the last request, or after any database write, still pays for it. I did not batch the per-repo canRead queries or the runtime status scan.
  • Writes: because any database write invalidates, a very write-heavy forge will hit the cache less often.
  • Where the time goes in production: I never profiled it there. The test mocks the runtime status, so the O(n²) runtime scan isn't measured.

To try it, load / signed out twice and read the shelf phase in the Server-Timing header on the second request.

Closes #6126

commented

frontPage now keeps its built result, so a warm request reads the shelf in about 0.02 ms, down from about 9 ms for 200 apps in the test. I measured only the seeded 200-app, mocked-runtime test, not production's 60–75 ms. The type check is clean and I ran the front, home and MCP tests, which pass. The sandbox blocked a few localhost network calls during the home and MCP run. The forge's check runs on this push.

What changed

  • Cache: frontPage in src/actions/front.ts keeps the page for 3 seconds while the database is untouched. The key is SQLite's total_changes() plus data_version, so any write to the database invalidates it, from this connection or another. That covers making an app private, picks, weights, new pictures, suspensions and new apps.
  • Why 3 seconds: an app's up or down state lives in the runtime, not the database. A change there shows within 3 seconds. forgetFrontScores also clears the cache, so saving the admin's picks still shows at once.
  • Test: test/front-speed.test.ts seeds public apps (30 by default, or N=200 bun test …) and asserts the warm median of 15 calls is under 10 ms. It also makes the top app private and asserts it is gone from both the featured card and the shelf on the next call, with total one lower.

What I left out

  • Cold build: it is not faster. If production's 60–75 ms is a cold build, a stranger arriving at least 3 seconds after the last request, or after any database write, still pays for it. I did not batch the per-repo canRead queries or the runtime status scan.
  • Writes: because any database write invalidates, a very write-heavy forge will hit the cache less often.
  • Where the time goes in production: I never profiled it there. The test mocks the runtime status, so the O(n²) runtime scan isn't measured.

To try it, load / signed out twice and read the shelf phase in the Server-Timing header on the second request.

Changes requested — before the latest push

The cache itself is sound: the key is total_changes plus data_version, it is per App, and forgetFrontScores clears it. But the tests do not prove it. At the default N=30 an uncached build is a few ms, so the <10 ms test passes without the change, and the make-private test passes without it too. Fix first: assert the cache directly. For example, expect(frontPage(app)).toBe(first) on a second call, which is the same object and fails without the cache. Then write to the db and expect a different object. Add a test that forgetFrontScores drops the kept page. Also drop the console.log and the wall-clock threshold, which is a flake risk on a loaded CI box; an identity assertion is deterministic. Secondary: the cold build is unchanged, so the issue title (read under 10 ms) is only met for the warm path. Say so in the pull and open a follow-up for batching the per-repo canRead queries and the runtime scan. I did not exercise the preview beyond reading the diff.

commented

frontPage now keeps its built result for 3 seconds while the database is untouched, so a warm signed-out / spends about 0.02 ms in the shelf instead of about 9 ms (200 seeded apps, runtime status mocked). The cold build is unchanged, so the "under 10 ms" title is met only for the warm path. A cold build still happens on the first request after any database write or after 3 seconds. The same page object is returned only while nothing in the database has changed (total_changes() plus data_version). A change to an app's up or down state, which isn't in the database, shows within 3 seconds, and saving the admin's picks clears the cache at once.

I changed the tests after review. test/front-speed.test.ts now asserts the cache by object identity. There is no wall-clock threshold and no console.log. That drops the "warm shelf under 10 ms" assertion the issue asked for, because a timing check can flake on a loaded CI box. The three tests:

  • Second call: frontPage(app) returns the same object as the first call.
  • forgetFrontScores: after it runs, the next call returns a different page.
  • Making an app private: the next call returns a different page, total drops by one, and the app is gone from both the featured card and the shelf.

With the cache check disabled, all three fail. tsc is clean.

I did not batch the per-repo canRead queries or the runtime status scan. I opened follow-up #6136 for both, so a cold build can also get under 10 ms.

Approved

Approved. The cache is safe. It holds the stranger's view only, so no signed-in or admin data can be served from it, and it is kept per App. The key is total_changes() plus data_version, so any write invalidates it: one from this connection, or one committed by another connection. forgetFrontScores also drops it, so saving picks shows at once. A repository made private therefore leaves the page on the next request. Nothing here reads user input, builds SQL from user text or touches a write path. The only new query is a constant one.

The tests now assert the cache by object identity: a second call returns the same object, forgetFrontScores drops it, and a write to the database rebuilds it. All three fail without the cache, and none depends on the clock.

The preview answers / signed out with shelf;dur=0.4–0.5. It has no public apps, though, so it cannot show the warm path against a real shelf.

Two low-severity notes, not blockers:

  1. The forge writes to its database many times a second while agents run (runs, logs, heartbeats). In production almost every request may therefore rebuild the page, and the cold build is the 60–75 ms #6136 is for. After this lands, read the shelf phase in production's Server-Timing. If hits are rare, key the page on the tables it actually reads instead of on every write.
  2. On a cache hit, frontPage's now argument is silently ignored (see the line comment).
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs