perf: stop the Overview sizing every relation on each render - #30
Merged
Merged
Conversation
On a 160 GB database with ~500 tables the two size queries behind the Overview ranked first and second in pg_stat_statements across every role: 62 s a call for the Largest tables chart, 26 s for the Database size card, both pg_total_relation_size() over all of pg_stat_user_tables and both outside the read-only executor's statement timeout. - Database size reads pg_database_size() through the read-only executor (1 s against 8 s on the same database, a 0.03% difference in value). - Largest tables no longer polls every five seconds. - The vitals card reads the cache hit ratio and active sessions once per render instead of three and two times. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What happened
We run Vacuum in a Filament panel on a 160 GB PostgreSQL (~500 tables). While chasing a CPU-saturated database,
pg_stat_statementsranked two Vacuum queries first and second across every role on the cluster, over three weeks:LargestTables—pg_total_relation_size()overpg_stat_user_tables, top 8DatabaseVitals::totalBytes()—sum(pg_total_relation_size())overpg_stat_user_tablesThat is ~80% of all the time our application role spent in the database, just by opening the Overview. Both queries have the server stat the files of every table, TOAST table and index one relation at a time, and both go through Eloquent rather than
ReadOnlyExecutor, so the executor's statement timeout never applies to them.The change
DatabaseVitals: database size frompg_database_size(current_database()), read throughReadOnlyExecutor. Same database, measured back to back:sum(pg_total_relation_size())8.1 s on a quiet server,pg_database_size()1.0 s. The value differs by 0.03% (the system catalogs and the ignored schemas are counted), which is what "Database size" means to the reader anyway.LargestTables:$pollingInterval = null.CanPolldefaults to'5s', so every five seconds the chart re-sized the whole database. Its answer does not change between polls; a reload redraws it.DatabaseVitals: each figure read once per render.cacheHitRatio()was queried three times (value, description, colour) andactiveSessions()twice.Nothing else changes on the page: same stats, same labels, same chart.
Tests
Three new tests in
tests/Filament/OverviewTest.php, each red onmainand green here:Bytes::human(pg_database_size());pg_stat_databaseread, one active-sessions read, nopg_total_relation_sizeper vitals render;LargestTablesdoes not poll.Both widgets stay at 100% line coverage (checked with Xdebug).
rector --dry-run,pint --testandphpstanare clean. Locally the suite has 37 failures, identical onmain: they all come frompg_stat_statements must be loaded via "shared_preload_libraries", because my Postgres is not the one indocker-compose.yml. CI should be unaffected.Left out on purpose
TableResource::getEloquentQuery()also orders the whole list bypg_total_relation_size(), so every load of the Tables list pays the same cost. I didn't touch it: making it cheap without losing exact ordering is a design choice (a size estimate frompg_class.relpagesgets stale enough to misorder real tables, as I found on our data), not a fix. Happy to open an issue for it if useful.🤖 Generated with Claude Code