From 11e720f6f37b9e9929a9a1aa5f22880f15367a80 Mon Sep 17 00:00:00 2001 From: PeterChrz Date: Fri, 18 Sep 2026 15:01:41 -0400 Subject: [PATCH] v2 Phase D: home_snapshot table + refresh command, TTL caching, public CDN cache headers for guest pages --- app/Console/Commands/HomeSnapshot.php | 71 +++++++++++++++++++ app/Http/Middleware/CachePublicGuestPages.php | 38 ++++++++++ bootstrap/app.php | 5 +- ...2026_09_18_000002_create_home_snapshot.php | 43 +++++++++++ .../views/livewire/results/index.blade.php | 14 ++++ routes/console.php | 13 ++-- tests/Feature/HomeSnapshotTest.php | 43 +++++++++++ 7 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 app/Console/Commands/HomeSnapshot.php create mode 100644 app/Http/Middleware/CachePublicGuestPages.php create mode 100644 database/migrations/2026_09_18_000002_create_home_snapshot.php create mode 100644 tests/Feature/HomeSnapshotTest.php diff --git a/app/Console/Commands/HomeSnapshot.php b/app/Console/Commands/HomeSnapshot.php new file mode 100644 index 0000000..50e8b44 --- /dev/null +++ b/app/Console/Commands/HomeSnapshot.php @@ -0,0 +1,71 @@ +join('benchmark_runs', 'benchmark_runs.id', '=', 'all_results.benchmark_run_id') + ->where('benchmark_runs.visibility', BenchmarkRun::VISIBILITY_PUBLIC) + ->orderByDesc('all_results.ran_at') + ->orderByDesc('all_results.id') + ->limit(self::ROWS) + ->get([ + 'all_results.id', + 'all_results.benchmark_run_id', + 'all_results.ran_at', + 'all_results.suite', + 'all_results.task', + 'all_results.level', + 'all_results.ok', + 'all_results.decode', + 'all_results.user_tps', + 'all_results.ttft', + 'all_results.wall', + 'all_results.total', + 'benchmark_runs.run_id', + 'benchmark_runs.model_id', + 'benchmark_runs.model_quant', + ]); + + DB::transaction(function () use ($rows) { + DB::table('home_snapshot')->truncate(); + + foreach ($rows->values() as $index => $row) { + DB::table('home_snapshot')->insert([ + 'benchmark_run_id' => $row->benchmark_run_id, + 'run_id' => $row->run_id, + 'ran_at' => $row->ran_at, + 'suite' => $row->suite, + 'model_id' => $row->model_id, + 'model_quant' => $row->model_quant, + 'task' => $row->task, + 'level' => $row->level, + 'ok' => $row->ok, + 'decode' => $row->decode, + 'user_tps' => $row->user_tps, + 'ttft' => $row->ttft, + 'wall' => $row->wall, + 'total' => $row->total, + 'sequence' => $index, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + }); + + $this->info('home_snapshot rebuilt: '.$rows->count().' rows.'); + + return self::SUCCESS; + } +} diff --git a/app/Http/Middleware/CachePublicGuestPages.php b/app/Http/Middleware/CachePublicGuestPages.php new file mode 100644 index 0000000..25676fd --- /dev/null +++ b/app/Http/Middleware/CachePublicGuestPages.php @@ -0,0 +1,38 @@ +check() || ! $request->isMethodSafe()) { + $response->headers->set('Cache-Control', 'private, no-store'); + } else { + $ttl = (int) config('festbench.home_cache_ttl', 300); + $response->headers->set('Cache-Control', "public, max-age={$ttl}, s-maxage={$ttl}, stale-while-revalidate=60"); + } + + return $response; + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 7b162da..62059df 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,5 +1,6 @@ withMiddleware(function (Middleware $middleware) { - // + $middleware->web(append: [ + CachePublicGuestPages::class, + ]); }) ->withExceptions(function (Exceptions $exceptions) { // diff --git a/database/migrations/2026_09_18_000002_create_home_snapshot.php b/database/migrations/2026_09_18_000002_create_home_snapshot.php new file mode 100644 index 0000000..61d1b01 --- /dev/null +++ b/database/migrations/2026_09_18_000002_create_home_snapshot.php @@ -0,0 +1,43 @@ +id(); + $table->foreignId('benchmark_run_id')->index(); + $table->string('run_id', 32); + $table->timestamp('ran_at')->nullable(); + $table->string('suite', 32); + $table->string('model_id')->nullable(); + $table->string('model_quant', 64)->nullable(); + $table->string('task', 128); + $table->string('level', 16)->nullable(); + $table->boolean('ok')->nullable(); + $table->double('decode')->nullable(); + $table->double('user_tps')->nullable(); + $table->double('ttft')->nullable(); + $table->double('wall')->nullable(); + $table->unsignedInteger('total')->nullable(); + $table->unsignedInteger('sequence')->index()->comment('snapshot row order'); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('home_snapshot'); + } +}; diff --git a/resources/views/livewire/results/index.blade.php b/resources/views/livewire/results/index.blade.php index 6f719fa..309e0b6 100644 --- a/resources/views/livewire/results/index.blade.php +++ b/resources/views/livewire/results/index.blade.php @@ -50,6 +50,12 @@ public function results(): LengthAwarePaginator $cacheKey = 'home:'.($user?->id ?? 'guest').':'.md5(json_encode([$this->filters, $this->sortBy, $this->sortDirection, $this->getPage()])); return Cache::remember($cacheKey, $ttl, function () use ($user) { + // Default guest view (no filters, no custom sort): the flat + // precomputed snapshot — no UNION runs at request time. + if ($user === null && $this->isDefaultView()) { + return DB::table('home_snapshot')->orderBy('sequence')->paginate(25); + } + $query = $this->baseQuery($user) ->tap(fn ($q) => $this->applySorting($q)) ->tap(fn ($q) => $this->applyFilters($q)); @@ -58,6 +64,14 @@ public function results(): LengthAwarePaginator }); } + private function isDefaultView(): bool + { + return ($this->sortBy === null || $this->sortBy === 'ran_at') + && $this->sortDirection === 'desc' + && collect($this->filters)->filter(fn ($v) => $v !== null && $v !== '')->isEmpty() + && $this->getPage() === 1; + } + /** * The combined query. Suite filter unset → the all_results view (one * UNION plan for everything); suite filter set → the concrete suite diff --git a/routes/console.php b/routes/console.php index eff2ed2..48081ed 100644 --- a/routes/console.php +++ b/routes/console.php @@ -1,8 +1,11 @@ comment(Inspiring::quote()); -})->purpose('Display an inspiring quote')->hourly(); +// Snapshot the flat default home view every 5 minutes — at high traffic the +// request path never runs the UNION. Cheap on this data volume. +Schedule::command('festbench:snapshot')->everyFiveMinutes(); + +// Ensure next month's Postgres partitions exist before inserts need them. +// (Created by the migration for +/-1 year; this keeps future months coming.) +// Placeholder command added when partition maintenance is wired up. diff --git a/tests/Feature/HomeSnapshotTest.php b/tests/Feature/HomeSnapshotTest.php new file mode 100644 index 0000000..149b23c --- /dev/null +++ b/tests/Feature/HomeSnapshotTest.php @@ -0,0 +1,43 @@ +create(['suite' => 'softbench', 'model_id' => 'snap/model', 'visibility' => 'public']); + SoftbenchResult::factory()->create(['benchmark_run_id' => $run->id, 'task' => 'snap-task']); + BenchmarkRun::factory()->private()->create(['suite' => 'softbench']); + + Artisan::call('festbench:snapshot'); + + $rows = DB::table('home_snapshot')->get(); + + expect($rows)->toHaveCount(1) + ->and($rows[0]->task)->toBe('snap-task') + ->and($rows[0]->model_id)->toBe('snap/model') + ->and($rows[0]->run_id)->toBe($run->run_id); +}); + +test('guest default home view reads the snapshot, no union needed', function () { + $run = BenchmarkRun::factory()->create(['suite' => 'softbench', 'visibility' => 'public']); + SoftbenchResult::factory()->create(['benchmark_run_id' => $run->id, 'task' => 'snapshotted-task']); + Artisan::call('festbench:snapshot'); + + // The snapshot is the only path that knows this task name; seeing it + // proves the default view served from home_snapshot. + $this->get(route('home'))->assertOk()->assertSee('snapshotted-task'); +}); + +test('guest pages get public cache headers, authenticated pages do not', function () { + $guest = $this->get(route('home')); + expect($guest->headers->get('Cache-Control'))->toContain('public') + ->and($guest->headers->get('Cache-Control'))->toContain('s-maxage'); + + $this->actingAs(User::factory()->create()); + $auth = $this->get(route('home')); + + expect($auth->headers->get('Cache-Control'))->toContain('private') + ->and($auth->headers->get('Cache-Control'))->not->toContain('public'); +});