Public: anyone can read this; the crew can change it.
/explore's rank stays warm on the live forge: a meter or heartbeat write no longer throws the ranking away #6259
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/6219-explore-s-rank-stays-warm-on-the-live-fo → main 1 commit fork 1 thread · 1 unresolved
2 files
changed +39
−4
src/core/rank_inputs.ts +21 −4
| @@ -15,12 +15,30 @@ | ||
| 15 | 15 | * |
| 16 | 16 | * An input added to the ranking belongs here. `test/explore-speed.test.ts` |
| 17 | 17 | * flips the forks, visibility, access, crew seats, app members and team |
| 18 | * grants and watches the answer follow; pulls, meters, user standing and the | |
| 18 | * grants and watches the answer follow; pulls, user standing and the | |
| 19 | 19 | * weights are hashed but not flipped there. |
| 20 | 20 | */ |
| 21 | 21 | |
| 22 | 22 | import type { Database } from "bun:sqlite"; |
| 23 | 23 | |
| 24 | /** The meter moves every few seconds on a busy forge; its part of the hash is read at most this often. */ | |
| 25 | export const METER_MAX_AGE_MS = 60_000; | |
| 26 | ||
| 27 | const METER_SQL = `SELECT count(*) || char(31) || COALESCE(max(rowid), 0) || char(31) || count(ended_at) || char(31) | |
| 28 | || COALESCE(max(ended_at), '') || char(31) || COALESCE(max(started_at), '') AS meter FROM meter`; | |
| 29 | ||
| 30 | const metered = new WeakMap<Database, { at: number; sign: string }>(); | |
| 31 | ||
| 32 | /** The meter's signature, kept for METER_MAX_AGE_MS so a run starting or ending does not move the hash. */ | |
| 33 | function meterSign(db: Database): string { | |
| 34 | const now = Date.now(); | |
| 35 | const kept = metered.get(db); | |
| 36 | if (kept !== undefined && now - kept.at < METER_MAX_AGE_MS && now >= kept.at) return kept.sign; | |
| 37 | const sign = db.query<{ meter: string }, []>(METER_SQL).get()?.meter ?? ""; | |
| 38 | metered.set(db, { at: now, sign }); | |
| 39 | return sign; | |
| 40 | } | |
| 41 | ||
| 24 | 42 | const SQL = `SELECT |
| 25 | 43 | (SELECT group_concat(id || char(31) || owner_id || char(31) || name || char(31) || visibility || char(31) |
| 26 | 44 | || description || char(31) || COALESCE(archived_at, '') || char(31) || COALESCE(fork_parent_id, ''), char(30)) FROM repo) AS repo, |
| @@ -33,13 +51,12 @@ const SQL = `SELECT | ||
| 33 | 51 | (SELECT group_concat(app_id || char(31) || user_id, char(30)) FROM app_member) AS named, |
| 34 | 52 | (SELECT count(*) || char(31) || COALESCE(max(rowid), 0) || char(31) || count(merged_at) || char(31) || COALESCE(max(merged_at), '') |
| 35 | 53 | FROM pull) AS pull, |
| 36 | (SELECT count(*) || char(31) || COALESCE(max(rowid), 0) || char(31) || count(ended_at) || char(31) | |
| 37 | || COALESCE(max(ended_at), '') || char(31) || COALESCE(max(started_at), '') FROM meter) AS meter, | |
| 38 | 54 | (SELECT group_concat(key || char(31) || value, char(30)) FROM front_setting) AS setting`; |
| 39 | 55 | |
| 40 | 56 | /** A hash of the ranking's inputs; cheap enough to ask on every request the write clock calls stale. */ |
| 41 | 57 | export function rankInputs(db: Database): string { |
| 42 | 58 | const row = db.query<Record<string, string | null>, []>(SQL).get(); |
| 43 | 59 | if (row === null) return ""; |
| 44 | return String(Bun.hash(Object.values(row).map((v) => v ?? "").join(String.fromCharCode(29)))); | |
| 60 | const parts = [...Object.values(row).map((v) => v ?? ""), meterSign(db)]; | |
| 61 | return String(Bun.hash(parts.join(String.fromCharCode(29)))); | |
| 45 | 62 | } |
test/explore-speed.test.ts +18
| @@ -54,6 +54,24 @@ test("a warm rank is under 10 ms, even after a write the ranking does not read", | ||
| 54 | 54 | expect(Math.min(...busy)).toBeLessThan(10); |
| 55 | 55 | }); |
| 56 | 56 | |
| 57 | test("a meter write between two requests does not rank again (#6219)", async () => { | |
| 58 | const before = await explore(f.app, undefined, all); | |
| 59 | await explore(f.app, undefined, all); | |
| 60 | const repo = f.repos.find((r) => r.visibility === "public")!; | |
| 61 | const busy: number[] = []; | |
| 62 | for (let i = 0; i < 5; i++) { | |
| 63 | f.app.db | |
| 64 | .query(`INSERT INTO meter (repo_id, env, placement, started_at, beat_at) VALUES (?1, 'production', 'owned', ?2, ?2)`) | |
| 65 | .run(repo.id, new Date(Date.now() + i).toISOString()); | |
| 66 | f.app.db.query("UPDATE meter SET ended_at = ?1 WHERE ended_at IS NULL").run(new Date().toISOString()); | |
| 67 | const t = performance.now(); | |
| 68 | const after = await explore(f.app, undefined, all); | |
| 69 | busy.push(performance.now() - t); | |
| 70 | expect(after[0]).toBe(before[0]); | |
| 71 | } | |
| 72 | expect(Math.min(...busy)).toBeLessThan(10); | |
| 73 | }); | |
| 74 | ||
| 57 | 75 | test("an unrelated write does not rank again", async () => { |
| 58 | 76 | const before = await explore(f.app, undefined, all); |
| 59 | 77 | expect(before.length).toBeGreaterThan(3); |
new above
offline
Nit: this duplicates
RANK_MAX_AGE_MSinsrc/actions/explore.ts. The comment ties the two together, but nothing enforces it. Consider exporting one constant and importing it in both places.