| Version 7 (modified by , 2 days ago) ( diff ) |
|---|
Indexes and performance analysis
Here we will cover the indexes that are not automatically created with PK/UK constraints, but would significantly improve performance in PC Forge. The focus is on the most frequent queries, JOIN operations and critical business scenarios.
With these indexes:
-Most frequent searches are covered (including build names, component names)
-Filtering by approval status and build dates is optimized
-JOIN operations between builds and components are faster
-Rating and favorite calculations are efficient
-Component type browsing is optimized
Scenario 1: Approved Builds Browsing and Filtering
Users frequently browse approved builds, filter them by name and sort by price, date or rating. This involves filtering by is_approved, searching by name and sorting by created_at or total_price.
Optimization: A composite index on is_approved and created_at speeds up the most common query pattern. Adding total_price allows for efficient sorting without additional table lookups.
CREATE INDEX IF NOT EXISTS idx_build_approved_date ON build(is_approved, created_at DESC); CREATE INDEX IF NOT EXISTS idx_build_approved_price ON build(is_approved, total_price DESC); CREATE INDEX IF NOT EXISTS idx_build_name_search ON build(name);
is_approved is a low-cardinality column (TRUE/FALSE), while both created_at and total_price are high-cardinality columns often used for ORDER BY, so that's why this combination is optimal.
Scenario 2: User Dashboard - Builds and Favorites
The user dashboard displays builds created by the user and also builds they've favorited, created by other users. This requires frequent lookups by user_id in both build and favorite_build tables, often with rating aggregations.
Optimization: Indexes on user_id in both tables, plus composite indexes for the JOIN operations between builds and ratings.
CREATE INDEX IF NOT EXISTS idx_build_user_id ON build(user_id); CREATE INDEX IF NOT EXISTS idx_favorite_build_user_id ON favorite_build(user_id); CREATE INDEX IF NOT EXISTS idx_rating_build_build_id ON rating_build(build_id);
Scenario 3: Build Components and Compatibility Checking
When users view build details or add components to builds, the application performs frequent JOIN operations between build_component and components. Compatibility checking also requires lookups by component type and specific attributes (like socket, TDP, form factor, length).
Optimization: Composite indexes on the junction table and component type filtering.
CREATE INDEX IF NOT EXISTS idx_build_component_build_id ON build_component(build_id); CREATE INDEX IF NOT EXISTS idx_build_component_component_id ON build_component(component_id); CREATE INDEX IF NOT EXISTS idx_components_type_price ON components(type, price DESC); CREATE INDEX IF NOT EXISTS idx_components_name_search ON components(name);
The composite index (type, price DESC) is really useful when it comes to component browsing dialogs, because they filter by type and sort by price by default.
Scenario 4: Admin Panel - Pending Approvals
Admins frequently check for pending builds and suggestions. This requires filtering by is_approved = FALSE for builds and status = 'pending' for component suggestions.
Optimization: Indexes on status columns with date ordering for priority sorting.
CREATE INDEX IF NOT EXISTS idx_build_pending_date ON build(is_approved, created_at DESC) WHERE is_approved = FALSE; CREATE INDEX IF NOT EXISTS idx_suggestions_status ON suggestions(status); CREATE INDEX IF NOT EXISTS idx_suggestions_user_id ON suggestions(user_id);
The partial index on build with WHERE is_approved = FALSE is smaller and more efficient, since admins only care about unapproved builds.
Scenario 5: Reviews and User Authentication
Users leave reviews on builds, commenting their likes and dislikes, and with that the application frequently looks up usernames for authentication and display purposes.
Optimization: Indexes for review lookups and username searches.
CREATE INDEX IF NOT EXISTS idx_review_build_id ON review(build_id); CREATE INDEX IF NOT EXISTS idx_review_user_id ON review(user_id); CREATE INDEX IF NOT EXISTS idx_users_username ON users(username); CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
username and email are both UNIQUE, but explicit indexes improve login query performance and case-insensitive searches if needed.
Scenario 6: Component Compatibility - Socket and Form Factor Matching
When users add components to their builds, the compatibility logic frequently queries CPU sockets, motherboard sockets and cooler compatibility.
Optimization: Indexes on compatibility-critical columns.
CREATE INDEX IF NOT EXISTS idx_cpu_socket ON cpu(socket); CREATE INDEX IF NOT EXISTS idx_motherboard_socket ON motherboard(socket); CREATE INDEX IF NOT EXISTS idx_cooler_sockets_socket ON cooler_cpu_sockets(socket); CREATE INDEX IF NOT EXISTS idx_motherboard_form_factor ON motherboard(form_factor);
Scenario 7: Top Performing Components Report
Users analyze which components are most popular in recent builds with high ratings. This report filters builds by date, joins multiple tables and aggregates ratings with a quality threshold.
Optimization: Without indexes, the query performs sequential scans on the build table to filter by created_at, then joins to rating_build.
A date index enables efficient range filtering, reducing the scan from 75000 builds to only those in the relevant time window.
CREATE INDEX IF NOT EXISTS idx_build_created_at ON build(created_at); CREATE INDEX IF NOT EXISTS idx_rating_build_build_value ON rating_build(build_id, value);
The date index on build(created_at) eliminates full table scans for time-windowed queries
Scenario 8: User Reputation Leaderboard Report
This report calculates user reputation scores by aggregating approved builds, favorites received, and average ratings per user.
Optimization: The partial index on approved builds significantly reduces the working dataset. Combined with indexes on the JOIN keys, this reduces buffer reads and eliminates unnecessary data processing.
CREATE INDEX IF NOT EXISTS idx_build_approved_user ON build(is_approved, user_id) WHERE is_approved = TRUE; CREATE INDEX IF NOT EXISTS idx_favorite_build_build_id ON favorite_build(build_id);
The composite partial index (is_approved, user_id) WHERE is_approved = TRUE is highly selective and perfect for analytics that only considers approved content.
Scenario 9: Budget Tier Popularity Report
This report segments builds into price tiers (Budget, Mid-Range, High-End, Enthusiast) and calculates engagement metrics within the last 6 months. It requires filtering by both approval status and creation date, then aggregates favorites and ratings for each tier.
Optimization: A composite index covering all three filter columns (is_approved, created_at, total_price) which enables index-only scans for the initial filter. The partial index with WHERE clause reduces index size by excluding unapproved builds entirely.
CREATE INDEX IF NOT EXISTS idx_build_approved_created_price ON build(is_approved, created_at, total_price) WHERE is_approved = TRUE;
Testing with EXPLAIN ANALYZE
Example test for scenario 4:
EXPLAIN (ANALYZE, BUFFERS) SELECT b.id, b.name, b.created_at, b.total_price, u.username FROM build b JOIN users u ON u.id = b.user_id WHERE b.is_approved = FALSE ORDER BY b.created_at DESC LIMIT 50;
Example test for scenario 7:
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM get_report_top_components();
Example test for scenario 8:
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM get_report_user_reputation_leaderboard();
Example test for scenario 9:
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM get_report_budget_tier_popularity();
Performance Comparison
Scenario 4: Admin Panel - Pending Approvals
Without partial index:
Index Scan using idx_build_approved_date on build b
Index Cond: (is_approved = false)
Buffers: shared hit=150
Execution Time: 0.351 ms
With partial index:
Index Scan using idx_build_pending_date on build b
Buffers: shared hit=68 read=2
Execution Time: 0.219 ms
Result: The partial index improved query performance by dropping the execution time from 0.351ms to 0.219ms.
Partial indexes are more efficient for frequently filtered subsets of data, so that's why they are ideal for the admin queries that only target pending builds.
Scenario 7: Top Performing Components Report
Without indexes:
"Limit (cost=24715.33..24715.37 rows=15 width=76) (actual time=514.170..521.754 rows=0.00 loops=1)" " Output: c.type, c.brand, c.name, (count(bc.component_id)), (avg(rb.value))" " Buffers: shared hit=7621" " -> Sort (cost=24715.33..24724.79 rows=3783 width=76) (actual time=514.169..521.752 rows=0.00 loops=1)" " Output: c.type, c.brand, c.name, (count(bc.component_id)), (avg(rb.value))" " Sort Key: (count(bc.component_id)) DESC, (avg(rb.value)) DESC" " Sort Method: quicksort Memory: 25kB" " Buffers: shared hit=7621" " -> Finalize HashAggregate (cost=24452.26..24622.51 rows=3783 width=76) (actual time=514.159..521.743 rows=0.00 loops=1)" " Output: c.type, c.brand, c.name, count(bc.component_id), avg(rb.value)" " Group Key: c.type, c.brand, c.name" " Filter: (avg(rb.value) >= 4.5)" " Batches: 1 Memory Usage: 289kB" " Rows Removed by Filter: 8" " Buffers: shared hit=7621" " -> Gather (cost=21177.79..24043.66 rows=27240 width=76) (actual time=512.069..521.649 rows=24.00 loops=1)" " Output: c.type, c.brand, c.name, (PARTIAL count(bc.component_id)), (PARTIAL avg(rb.value))" " Workers Planned: 2" " Workers Launched: 2" " Buffers: shared hit=7621" " -> Partial HashAggregate (cost=20177.79..20319.66 rows=11350 width=76) (actual time=477.550..477.607 rows=8.00 loops=3)" " Output: c.type, c.brand, c.name, PARTIAL count(bc.component_id), PARTIAL avg(rb.value)" " Group Key: c.type, c.brand, c.name" " Batches: 1 Memory Usage: 289kB" " Buffers: shared hit=7621" " Worker 0: actual time=465.044..465.084 rows=8.00 loops=1" " Batches: 1 Memory Usage: 289kB" " Buffers: shared hit=2485" " Worker 1: actual time=456.082..456.143 rows=8.00 loops=1" " Batches: 1 Memory Usage: 289kB" " Buffers: shared hit=2404" " -> Hash Join (cost=6196.24..16477.60 rows=296015 width=45) (actual time=143.788..343.710 rows=236717.33 loops=3)" " Output: c.type, c.brand, c.name, bc.component_id, rb.value" " Hash Cond: (bc.build_id = b.id)" " Buffers: shared hit=7621" " Worker 0: actual time=158.769..344.695 rows=224941.00 loops=1" " Buffers: shared hit=2485" " Worker 1: actual time=153.000..337.176 rows=207233.00 loops=1" " Buffers: shared hit=2404" " -> Hash Join (cost=368.38..6768.87 rows=250000 width=44) (actual time=6.150..89.973 rows=200000.00 loops=3)" " Output: c.type, c.brand, c.name, bc.component_id, bc.build_id" " Inner Unique: true" " Hash Cond: (bc.component_id = c.id)" " Buffers: shared hit=3583" " Worker 0: actual time=7.786..86.909 rows=189810.00 loops=1" " Buffers: shared hit=1139" " Worker 1: actual time=7.464..86.431 rows=174825.00 loops=1" " Buffers: shared hit=1058" " -> Parallel Seq Scan on public.build_component bc (cost=0.00..5744.00 rows=250000 width=8) (actual time=0.013..16.572 rows=200000.00 loops=3)" " Output: bc.build_id, bc.component_id, bc.num_components" " Buffers: shared hit=3244" " Worker 0: actual time=0.018..16.106 rows=189810.00 loops=1" " Buffers: shared hit=1026" " Worker 1: actual time=0.017..15.861 rows=174825.00 loops=1" " Buffers: shared hit=945" " -> Hash (cost=226.50..226.50 rows=11350 width=40) (actual time=6.054..6.055 rows=11350.00 loops=3)" " Output: c.type, c.brand, c.name, c.id" " Buckets: 16384 Batches: 1 Memory Usage: 962kB" " Buffers: shared hit=339" " Worker 0: actual time=7.679..7.679 rows=11350.00 loops=1" " Buffers: shared hit=113" " Worker 1: actual time=7.350..7.350 rows=11350.00 loops=1" " Buffers: shared hit=113" " -> Seq Scan on public.components c (cost=0.00..226.50 rows=11350 width=40) (actual time=0.344..2.832 rows=11350.00 loops=3)" " Output: c.type, c.brand, c.name, c.id" " Buffers: shared hit=339" " Worker 0: actual time=0.450..3.615 rows=11350.00 loops=1" " Buffers: shared hit=113" " Worker 1: actual time=0.571..3.620 rows=11350.00 loops=1" " Buffers: shared hit=113" " -> Hash (cost=4717.80..4717.80 rows=88805 width=13) (actual time=137.365..137.366 rows=88769.00 loops=3)" " Output: b.id, rb.value, rb.build_id" " Buckets: 131072 Batches: 1 Memory Usage: 5186kB" " Buffers: shared hit=4038" " Worker 0: actual time=150.702..150.703 rows=88769.00 loops=1" " Buffers: shared hit=1346" " Worker 1: actual time=145.234..145.235 rows=88769.00 loops=1" " Buffers: shared hit=1346" " -> Hash Join (cost=3110.85..4717.80 rows=88805 width=13) (actual time=45.967..109.261 rows=88769.00 loops=3)" " Output: b.id, rb.value, rb.build_id" " Inner Unique: true" " Hash Cond: (rb.build_id = b.id)" " Buffers: shared hit=4038" " Worker 0: actual time=51.541..120.247 rows=88769.00 loops=1" " Buffers: shared hit=1346" " Worker 1: actual time=46.388..115.886 rows=88769.00 loops=1" " Buffers: shared hit=1346" " -> Seq Scan on public.rating_build rb (cost=0.00..1373.04 rows=89104 width=9) (actual time=0.407..8.817 rows=89104.00 loops=3)" " Output: rb.build_id, rb.user_id, rb.value" " Buffers: shared hit=1446" " Worker 0: actual time=0.566..8.558 rows=89104.00 loops=1" " Buffers: shared hit=482" " Worker 1: actual time=0.638..11.340 rows=89104.00 loops=1" " Buffers: shared hit=482" " -> Hash (cost=2176.50..2176.50 rows=74748 width=4) (actual time=45.199..45.200 rows=74720.00 loops=3)" " Output: b.id" " Buckets: 131072 Batches: 1 Memory Usage: 3651kB" " Buffers: shared hit=2592" " Worker 0: actual time=50.414..50.414 rows=74720.00 loops=1" " Buffers: shared hit=864" " Worker 1: actual time=45.487..45.487 rows=74720.00 loops=1" " Buffers: shared hit=864" " -> Seq Scan on public.build b (cost=0.00..2176.50 rows=74748 width=4) (actual time=0.354..27.279 rows=74720.00 loops=3)" " Output: b.id" " Filter: (b.created_at >= (CURRENT_DATE - '1 year'::interval))" " Rows Removed by Filter: 280" " Buffers: shared hit=2592" " Worker 0: actual time=0.594..30.487 rows=74720.00 loops=1" " Buffers: shared hit=864" " Worker 1: actual time=0.450..28.109 rows=74720.00 loops=1" " Buffers: shared hit=864" "Planning:" " Buffers: shared hit=61 dirtied=1" "Planning Time: 2.660 ms" "Execution Time: 523.396 ms"
With indexes:
"Limit (cost=23597.58..23597.61 rows=15 width=76) (actual time=450.819..457.739 rows=0.00 loops=1)" " Output: c.type, c.brand, c.name, (count(bc.component_id)), (avg(rb.value))" " Buffers: shared hit=6180 read=340" " -> Sort (cost=23597.58..23607.03 rows=3783 width=76) (actual time=450.818..457.738 rows=0.00 loops=1)" " Output: c.type, c.brand, c.name, (count(bc.component_id)), (avg(rb.value))" " Sort Key: (count(bc.component_id)) DESC, (avg(rb.value)) DESC" " Sort Method: quicksort Memory: 25kB" " Buffers: shared hit=6180 read=340" " -> Finalize HashAggregate (cost=23334.51..23504.76 rows=3783 width=76) (actual time=450.813..457.732 rows=0.00 loops=1)" " Output: c.type, c.brand, c.name, count(bc.component_id), avg(rb.value)" " Group Key: c.type, c.brand, c.name" " Filter: (avg(rb.value) >= 4.5)" " Batches: 1 Memory Usage: 289kB" " Rows Removed by Filter: 8" " Buffers: shared hit=6180 read=340" " -> Gather (cost=20060.04..22925.91 rows=27240 width=76) (actual time=449.068..457.644 rows=24.00 loops=1)" " Output: c.type, c.brand, c.name, (PARTIAL count(bc.component_id)), (PARTIAL avg(rb.value))" " Workers Planned: 2" " Workers Launched: 2" " Buffers: shared hit=6180 read=340" " -> Partial HashAggregate (cost=19060.04..19201.91 rows=11350 width=76) (actual time=419.604..419.656 rows=8.00 loops=3)" " Output: c.type, c.brand, c.name, PARTIAL count(bc.component_id), PARTIAL avg(rb.value)" " Group Key: c.type, c.brand, c.name" " Batches: 1 Memory Usage: 289kB" " Buffers: shared hit=6180 read=340" " Worker 0: actual time=406.116..406.170 rows=8.00 loops=1" " Batches: 1 Memory Usage: 289kB" " Buffers: shared hit=2093" " Worker 1: actual time=404.070..404.112 rows=8.00 loops=1" " Batches: 1 Memory Usage: 289kB" " Buffers: shared hit=1996" " -> Parallel Hash Join (cost=6144.52..15359.85 rows=296015 width=45) (actual time=75.562..301.742 rows=236717.33 loops=3)" " Output: c.type, c.brand, c.name, bc.component_id, rb.value" " Hash Cond: (bc.build_id = rb.build_id)" " Buffers: shared hit=6180 read=340" " Worker 0: actual time=69.636..294.647 rows=244174.00 loops=1" " Buffers: shared hit=2093" " Worker 1: actual time=65.038..288.820 rows=222711.00 loops=1" " Buffers: shared hit=1996" " -> Hash Join (cost=3479.22..10533.80 rows=249160 width=48) (actual time=64.759..205.120 rows=199253.33 loops=3)" " Output: c.type, c.brand, c.name, bc.component_id, bc.build_id, b.id" " Inner Unique: true" " Hash Cond: (bc.component_id = c.id)" " Buffers: shared hit=6175" " Worker 0: actual time=69.584..208.007 rows=205692.00 loops=1" " Buffers: shared hit=2093" " Worker 1: actual time=64.969..204.957 rows=187846.00 loops=1" " Buffers: shared hit=1996" " -> Hash Join (cost=3110.85..9511.13 rows=249160 width=12) (actual time=59.879..150.606 rows=199253.33 loops=3)" " Output: bc.component_id, bc.build_id, b.id" " Inner Unique: true" " Hash Cond: (bc.build_id = b.id)" " Buffers: shared hit=5836" " Worker 0: actual time=63.648..155.149 rows=205692.00 loops=1" " Buffers: shared hit=1980" " Worker 1: actual time=61.520..151.281 rows=187846.00 loops=1" " Buffers: shared hit=1883" " -> Parallel Seq Scan on public.build_component bc (cost=0.00..5744.00 rows=250000 width=8) (actual time=0.007..14.818 rows=200000.00 loops=3)" " Output: bc.build_id, bc.component_id, bc.num_components" " Buffers: shared hit=3244" " Worker 0: actual time=0.010..15.585 rows=206460.00 loops=1" " Buffers: shared hit=1116" " Worker 1: actual time=0.008..15.532 rows=188515.00 loops=1" " Buffers: shared hit=1019" " -> Hash (cost=2176.50..2176.50 rows=74748 width=4) (actual time=59.628..59.628 rows=74720.00 loops=3)" " Output: b.id" " Buckets: 131072 Batches: 1 Memory Usage: 3651kB" " Buffers: shared hit=2592" " Worker 0: actual time=63.410..63.410 rows=74720.00 loops=1" " Buffers: shared hit=864" " Worker 1: actual time=61.314..61.314 rows=74720.00 loops=1" " Buffers: shared hit=864" " -> Seq Scan on public.build b (cost=0.00..2176.50 rows=74748 width=4) (actual time=0.289..34.946 rows=74720.00 loops=3)" " Output: b.id" " Filter: (b.created_at >= (CURRENT_DATE - '1 year'::interval))" " Rows Removed by Filter: 280" " Buffers: shared hit=2592" " Worker 0: actual time=0.417..34.850 rows=74720.00 loops=1" " Buffers: shared hit=864" " Worker 1: actual time=0.422..41.270 rows=74720.00 loops=1" " Buffers: shared hit=864" " -> Hash (cost=226.50..226.50 rows=11350 width=40) (actual time=4.786..4.788 rows=11350.00 loops=3)" " Output: c.type, c.brand, c.name, c.id" " Buckets: 16384 Batches: 1 Memory Usage: 962kB" " Buffers: shared hit=339" " Worker 0: actual time=5.790..5.790 rows=11350.00 loops=1" " Buffers: shared hit=113" " Worker 1: actual time=3.373..3.374 rows=11350.00 loops=1" " Buffers: shared hit=113" " -> Seq Scan on public.components c (cost=0.00..226.50 rows=11350 width=40) (actual time=0.191..2.117 rows=11350.00 loops=3)" " Output: c.type, c.brand, c.name, c.id" " Buffers: shared hit=339" " Worker 0: actual time=0.264..2.779 rows=11350.00 loops=1" " Buffers: shared hit=113" " Worker 1: actual time=0.290..1.620 rows=11350.00 loops=1" " Buffers: shared hit=113" " -> Parallel Hash (cost=2201.20..2201.20 rows=37127 width=9) (actual time=10.621..10.622 rows=29701.33 loops=3)" " Output: rb.value, rb.build_id" " Buckets: 131072 Batches: 1 Memory Usage: 5216kB" " Buffers: shared hit=5 read=340" " Worker 0: actual time=0.024..0.024 rows=0.00 loops=1" " Worker 1: actual time=0.017..0.017 rows=0.00 loops=1" " -> Parallel Index Only Scan using idx_rating_build_build_value on public.rating_build rb (cost=0.42..2201.20 rows=37127 width=9) (actual time=0.015..14.847 rows=89104.00 loops=1)" " Output: rb.value, rb.build_id" " Heap Fetches: 0" " Index Searches: 1" " Buffers: shared hit=5 read=340" "Planning:" " Buffers: shared hit=84 read=9" "Planning Time: 3.357 ms" "Execution Time: 458.529 ms"
Result: The date index improved query performance, while not much, still a modest improvement reducing it from 523.396ms to 458.529ms.
Scenario 8: User Reputation Leaderboard Report
Without indexes:
"Limit (cost=13848.06..13848.08 rows=10 width=135) (actual time=314.741..314.745 rows=10.00 loops=1)" " Output: u.username, u.email, (count(b.id)), (COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric)), (round(COALESCE((avg((avg(rb.value)))), '0'::numeric), 2)), ((((((count(b.id)) * 10))::numeric + ((COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric)) * '5'::numeric)) + (COALESCE((avg((avg(rb.value)))), '0'::numeric) * '20'::numeric)))" " Buffers: shared hit=142841, temp read=231 written=584" " -> Sort (cost=13848.06..13848.56 rows=200 width=135) (actual time=314.740..314.743 rows=10.00 loops=1)" " Output: u.username, u.email, (count(b.id)), (COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric)), (round(COALESCE((avg((avg(rb.value)))), '0'::numeric), 2)), ((((((count(b.id)) * 10))::numeric + ((COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric)) * '5'::numeric)) + (COALESCE((avg((avg(rb.value)))), '0'::numeric) * '20'::numeric)))" " Sort Key: ((((((count(b.id)) * 10))::numeric + ((COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric)) * '5'::numeric)) + (COALESCE((avg((avg(rb.value)))), '0'::numeric) * '20'::numeric))) DESC" " Sort Method: top-N heapsort Memory: 26kB" " Buffers: shared hit=142841, temp read=231 written=584" " -> Nested Loop (cost=12461.53..13843.74 rows=200 width=135) (actual time=133.050..301.004 rows=47142.00 loops=1)" " Output: u.username, u.email, (count(b.id)), (COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric)), round(COALESCE((avg((avg(rb.value)))), '0'::numeric), 2), (((((count(b.id)) * 10))::numeric + ((COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric)) * '5'::numeric)) + (COALESCE((avg((avg(rb.value)))), '0'::numeric) * '20'::numeric))" " Inner Unique: true" " Buffers: shared hit=142841, temp read=231 written=584" " -> HashAggregate (cost=12461.24..12464.24 rows=200 width=76) (actual time=133.026..193.252 rows=47142.00 loops=1)" " Output: b.user_id, count(b.id), COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric), avg((avg(rb.value)))" " Group Key: b.user_id" " Batches: 5 Memory Usage: 8257kB Disk Usage: 3344kB" " Buffers: shared hit=1415, temp read=231 written=584" " -> GroupAggregate (cost=0.84..11187.64 rows=63680 width=48) (actual time=0.068..104.662 rows=63679.00 loops=1)" " Output: b.id, b.user_id, count(DISTINCT fb.user_id), avg(rb.value)" " Group Key: b.id" " Buffers: shared hit=1415" " -> Incremental Sort (cost=0.84..9824.23 rows=75655 width=17) (actual time=0.060..59.549 rows=94843.00 loops=1)" " Output: b.id, b.user_id, fb.user_id, rb.value" " Sort Key: b.id, fb.user_id" " Presorted Key: b.id" " Full-sort Groups: 2934 Sort Method: quicksort Average Memory: 25kB Peak Memory: 25kB" " Buffers: shared hit=1415" " -> Merge Left Join (cost=0.72..6838.88 rows=75655 width=17) (actual time=0.038..44.233 rows=94843.00 loops=1)" " Output: b.id, b.user_id, fb.user_id, rb.value" " Merge Cond: (b.id = rb.build_id)" " Buffers: shared hit=1415" " -> Merge Left Join (cost=0.30..2979.52 rows=63680 width=12) (actual time=0.026..20.287 rows=63679.00 loops=1)" " Output: b.id, b.user_id, fb.user_id" " Merge Cond: (b.id = fb.build_id)" " Buffers: shared hit=1070" " -> Index Scan using build_pkey on public.build b (cost=0.29..2820.29 rows=63680 width=8) (actual time=0.010..15.036 rows=63679.00 loops=1)" " Output: b.id, b.user_id, b.name, b.created_at, b.description, b.total_price, b.is_approved" " Filter: b.is_approved" " Rows Removed by Filter: 11321" " Index Searches: 1" " Buffers: shared hit=1070" " -> Sort (cost=0.01..0.02 rows=1 width=8) (actual time=0.015..0.015 rows=0.00 loops=1)" " Output: fb.user_id, fb.build_id" " Sort Key: fb.build_id" " Sort Method: quicksort Memory: 25kB" " -> Seq Scan on public.favorite_build fb (cost=0.00..0.00 rows=1 width=8) (actual time=0.009..0.010 rows=0.00 loops=1)" " Output: fb.user_id, fb.build_id" " -> Index Only Scan using idx_rating_build_build_value on public.rating_build rb (cost=0.42..2720.98 rows=89104 width=9) (actual time=0.010..8.113 rows=89104.00 loops=1)" " Output: rb.build_id, rb.value" " Heap Fetches: 0" " Index Searches: 1" " Buffers: shared hit=345" " -> Index Scan using users_pkey on public.users u (cost=0.29..6.87 rows=1 width=35) (actual time=0.002..0.002 rows=1.00 loops=47142)" " Output: u.id, u.username, u.password, u.email" " Index Cond: (u.id = b.user_id)" " Index Searches: 47142" " Buffers: shared hit=141426" "Planning:" " Buffers: shared hit=37 dirtied=1" "Planning Time: 1.422 ms" "Execution Time: 325.769 ms"
With indexes:
"Limit (cost=13848.06..13848.08 rows=10 width=135) (actual time=293.344..293.349 rows=10.00 loops=1)" " Output: u.username, u.email, (count(b.id)), (COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric)), (round(COALESCE((avg((avg(rb.value)))), '0'::numeric), 2)), ((((((count(b.id)) * 10))::numeric + ((COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric)) * '5'::numeric)) + (COALESCE((avg((avg(rb.value)))), '0'::numeric) * '20'::numeric)))" " Buffers: shared hit=142841, temp read=231 written=584" " -> Sort (cost=13848.06..13848.56 rows=200 width=135) (actual time=293.343..293.347 rows=10.00 loops=1)" " Output: u.username, u.email, (count(b.id)), (COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric)), (round(COALESCE((avg((avg(rb.value)))), '0'::numeric), 2)), ((((((count(b.id)) * 10))::numeric + ((COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric)) * '5'::numeric)) + (COALESCE((avg((avg(rb.value)))), '0'::numeric) * '20'::numeric)))" " Sort Key: ((((((count(b.id)) * 10))::numeric + ((COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric)) * '5'::numeric)) + (COALESCE((avg((avg(rb.value)))), '0'::numeric) * '20'::numeric))) DESC" " Sort Method: top-N heapsort Memory: 26kB" " Buffers: shared hit=142841, temp read=231 written=584" " -> Nested Loop (cost=12461.53..13843.74 rows=200 width=135) (actual time=125.883..280.717 rows=47142.00 loops=1)" " Output: u.username, u.email, (count(b.id)), (COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric)), round(COALESCE((avg((avg(rb.value)))), '0'::numeric), 2), (((((count(b.id)) * 10))::numeric + ((COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric)) * '5'::numeric)) + (COALESCE((avg((avg(rb.value)))), '0'::numeric) * '20'::numeric))" " Inner Unique: true" " Buffers: shared hit=142841, temp read=231 written=584" " -> HashAggregate (cost=12461.24..12464.24 rows=200 width=76) (actual time=125.860..181.260 rows=47142.00 loops=1)" " Output: b.user_id, count(b.id), COALESCE(sum((count(DISTINCT fb.user_id))), '0'::numeric), avg((avg(rb.value)))" " Group Key: b.user_id" " Batches: 5 Memory Usage: 8257kB Disk Usage: 3344kB" " Buffers: shared hit=1415, temp read=231 written=584" " -> GroupAggregate (cost=0.84..11187.64 rows=63680 width=48) (actual time=0.060..98.939 rows=63679.00 loops=1)" " Output: b.id, b.user_id, count(DISTINCT fb.user_id), avg(rb.value)" " Group Key: b.id" " Buffers: shared hit=1415" " -> Incremental Sort (cost=0.84..9824.23 rows=75655 width=17) (actual time=0.055..56.113 rows=94843.00 loops=1)" " Output: b.id, b.user_id, fb.user_id, rb.value" " Sort Key: b.id, fb.user_id" " Presorted Key: b.id" " Full-sort Groups: 2934 Sort Method: quicksort Average Memory: 25kB Peak Memory: 25kB" " Buffers: shared hit=1415" " -> Merge Left Join (cost=0.72..6838.88 rows=75655 width=17) (actual time=0.032..41.184 rows=94843.00 loops=1)" " Output: b.id, b.user_id, fb.user_id, rb.value" " Merge Cond: (b.id = rb.build_id)" " Buffers: shared hit=1415" " -> Merge Left Join (cost=0.30..2979.52 rows=63680 width=12) (actual time=0.022..18.581 rows=63679.00 loops=1)" " Output: b.id, b.user_id, fb.user_id" " Merge Cond: (b.id = fb.build_id)" " Buffers: shared hit=1070" " -> Index Scan using build_pkey on public.build b (cost=0.29..2820.29 rows=63680 width=8) (actual time=0.009..13.763 rows=63679.00 loops=1)" " Output: b.id, b.user_id, b.name, b.created_at, b.description, b.total_price, b.is_approved" " Filter: b.is_approved" " Rows Removed by Filter: 11321" " Index Searches: 1" " Buffers: shared hit=1070" " -> Sort (cost=0.01..0.02 rows=1 width=8) (actual time=0.011..0.011 rows=0.00 loops=1)" " Output: fb.user_id, fb.build_id" " Sort Key: fb.build_id" " Sort Method: quicksort Memory: 25kB" " -> Seq Scan on public.favorite_build fb (cost=0.00..0.00 rows=1 width=8) (actual time=0.007..0.007 rows=0.00 loops=1)" " Output: fb.user_id, fb.build_id" " -> Index Only Scan using idx_rating_build_build_value on public.rating_build rb (cost=0.42..2720.98 rows=89104 width=9) (actual time=0.009..7.694 rows=89104.00 loops=1)" " Output: rb.build_id, rb.value" " Heap Fetches: 0" " Index Searches: 1" " Buffers: shared hit=345" " -> Index Scan using users_pkey on public.users u (cost=0.29..6.87 rows=1 width=35) (actual time=0.002..0.002 rows=1.00 loops=47142)" " Output: u.id, u.username, u.password, u.email" " Index Cond: (u.id = b.user_id)" " Index Searches: 47142" " Buffers: shared hit=141426" "Planning:" " Buffers: shared hit=60 read=2" "Planning Time: 2.444 ms" "Execution Time: 304.156 ms"
Result: Here the improvement is modest but still, the time is reduced from 325.769ms to 304.156ms. The difference isn't that big because both queries use the same access path.
Scenario 9: Budget Tier Popularity Report
Without indexes:
"Limit (cost=10349.54..10349.58 rows=15 width=144) (actual time=129.156..129.161 rows=3.00 loops=1)" " Output: engagement_stats.price_tier, engagement_stats.builds_count, (round(engagement_stats.avg_favorites, 1)), (round(COALESCE(engagement_stats.avg_rating, '0'::numeric), 2)), engagement_stats.unique_builders, (round((((((engagement_stats.builds_count * 2))::numeric + (engagement_stats.avg_favorites * '3'::numeric)) + (COALESCE(engagement_stats.avg_rating, '0'::numeric) * '10'::numeric)) + ((engagement_stats.unique_builders)::numeric * 1.5)), 2))" " Buffers: shared hit=1346" " -> Sort (cost=10349.54..10426.19 rows=30660 width=144) (actual time=129.155..129.159 rows=3.00 loops=1)" " Output: engagement_stats.price_tier, engagement_stats.builds_count, (round(engagement_stats.avg_favorites, 1)), (round(COALESCE(engagement_stats.avg_rating, '0'::numeric), 2)), engagement_stats.unique_builders, (round((((((engagement_stats.builds_count * 2))::numeric + (engagement_stats.avg_favorites * '3'::numeric)) + (COALESCE(engagement_stats.avg_rating, '0'::numeric) * '10'::numeric)) + ((engagement_stats.unique_builders)::numeric * 1.5)), 2))" " Sort Key: (round((((((engagement_stats.builds_count * 2))::numeric + (engagement_stats.avg_favorites * '3'::numeric)) + (COALESCE(engagement_stats.avg_rating, '0'::numeric) * '10'::numeric)) + ((engagement_stats.unique_builders)::numeric * 1.5)), 2)) DESC" " Sort Method: quicksort Memory: 25kB" " Buffers: shared hit=1346" " -> Subquery Scan on engagement_stats (cost=7422.84..9597.32 rows=30660 width=144) (actual time=120.760..129.147 rows=3.00 loops=1)" " Output: engagement_stats.price_tier, engagement_stats.builds_count, round(engagement_stats.avg_favorites, 1), round(COALESCE(engagement_stats.avg_rating, '0'::numeric), 2), engagement_stats.unique_builders, round((((((engagement_stats.builds_count * 2))::numeric + (engagement_stats.avg_favorites * '3'::numeric)) + (COALESCE(engagement_stats.avg_rating, '0'::numeric) * '10'::numeric)) + ((engagement_stats.unique_builders)::numeric * 1.5)), 2)" " Buffers: shared hit=1346" " -> GroupAggregate (cost=7422.84..8677.52 rows=30660 width=112) (actual time=120.745..129.125 rows=3.00 loops=1)" " Output: (CASE WHEN (b.total_price < '500'::numeric) THEN 'Budget'::text WHEN (b.total_price < '1000'::numeric) THEN 'Mid-Range'::text WHEN (b.total_price < '2000'::numeric) THEN 'High-End'::text ELSE 'Enthusiast'::text END), count(DISTINCT b.id), avg(COALESCE(f.favorites_count, '0'::bigint)), avg(rb.value), count(DISTINCT b.user_id)" " Group Key: (CASE WHEN (b.total_price < '500'::numeric) THEN 'Budget'::text WHEN (b.total_price < '1000'::numeric) THEN 'Mid-Range'::text WHEN (b.total_price < '2000'::numeric) THEN 'High-End'::text ELSE 'Enthusiast'::text END)" " Buffers: shared hit=1346" " -> Sort (cost=7422.84..7516.98 rows=37655 width=53) (actual time=107.389..110.953 rows=47343.00 loops=1)" " Output: (CASE WHEN (b.total_price < '500'::numeric) THEN 'Budget'::text WHEN (b.total_price < '1000'::numeric) THEN 'Mid-Range'::text WHEN (b.total_price < '2000'::numeric) THEN 'High-End'::text ELSE 'Enthusiast'::text END), b.id, f.favorites_count, rb.value, b.user_id" " Sort Key: (CASE WHEN (b.total_price < '500'::numeric) THEN 'Budget'::text WHEN (b.total_price < '1000'::numeric) THEN 'Mid-Range'::text WHEN (b.total_price < '2000'::numeric) THEN 'High-End'::text ELSE 'Enthusiast'::text END), b.id" " Sort Method: quicksort Memory: 3562kB" " Buffers: shared hit=1346" " -> Hash Left Join (cost=2572.74..4560.96 rows=37655 width=53) (actual time=16.527..54.066 rows=47343.00 loops=1)" " Output: CASE WHEN (b.total_price < '500'::numeric) THEN 'Budget'::text WHEN (b.total_price < '1000'::numeric) THEN 'Mid-Range'::text WHEN (b.total_price < '2000'::numeric) THEN 'High-End'::text ELSE 'Enthusiast'::text END, b.id, f.favorites_count, rb.value, b.user_id" " Inner Unique: true" " Hash Cond: (b.id = f.build_id)" " Buffers: shared hit=1346" " -> Hash Right Join (cost=2572.69..4179.64 rows=37655 width=19) (actual time=16.499..43.795 rows=47343.00 loops=1)" " Output: b.total_price, b.id, b.user_id, rb.value" " Inner Unique: true" " Hash Cond: (rb.build_id = b.id)" " Buffers: shared hit=1346" " -> Seq Scan on public.rating_build rb (cost=0.00..1373.04 rows=89104 width=9) (actual time=0.019..3.738 rows=89104.00 loops=1)" " Output: rb.build_id, rb.user_id, rb.value" " Buffers: shared hit=482" " -> Hash (cost=2176.50..2176.50 rows=31695 width=14) (actual time=16.413..16.414 rows=31774.00 loops=1)" " Output: b.total_price, b.id, b.user_id" " Buckets: 32768 Batches: 1 Memory Usage: 1746kB" " Buffers: shared hit=864" " -> Seq Scan on public.build b (cost=0.00..2176.50 rows=31695 width=14) (actual time=0.012..12.589 rows=31774.00 loops=1)" " Output: b.total_price, b.id, b.user_id" " Filter: (b.is_approved AND (b.created_at >= (CURRENT_DATE - '6 mons'::interval)))" " Rows Removed by Filter: 43226" " Buffers: shared hit=864" " -> Hash (cost=0.04..0.04 rows=1 width=12) (actual time=0.017..0.019 rows=0.00 loops=1)" " Output: f.favorites_count, f.build_id" " Buckets: 1024 Batches: 1 Memory Usage: 8kB" " -> Subquery Scan on f (cost=0.01..0.04 rows=1 width=12) (actual time=0.017..0.018 rows=0.00 loops=1)" " Output: f.favorites_count, f.build_id" " -> GroupAggregate (cost=0.01..0.03 rows=1 width=12) (actual time=0.016..0.017 rows=0.00 loops=1)" " Output: favorite_build.build_id, count(DISTINCT favorite_build.user_id)" " Group Key: favorite_build.build_id" " -> Sort (cost=0.01..0.02 rows=1 width=8) (actual time=0.015..0.016 rows=0.00 loops=1)" " Output: favorite_build.build_id, favorite_build.user_id" " Sort Key: favorite_build.build_id, favorite_build.user_id" " Sort Method: quicksort Memory: 25kB" " -> Seq Scan on public.favorite_build (cost=0.00..0.00 rows=1 width=8) (actual time=0.008..0.009 rows=0.00 loops=1)" " Output: favorite_build.build_id, favorite_build.user_id" "Planning:" " Buffers: shared hit=26 dirtied=1" "Planning Time: 1.348 ms" "Execution Time: 129.409 ms"
With indexes:
"Limit (cost=10242.59..10242.63 rows=15 width=144) (actual time=125.400..125.404 rows=3.00 loops=1)" " Output: engagement_stats.price_tier, engagement_stats.builds_count, (round(engagement_stats.avg_favorites, 1)), (round(COALESCE(engagement_stats.avg_rating, '0'::numeric), 2)), engagement_stats.unique_builders, (round((((((engagement_stats.builds_count * 2))::numeric + (engagement_stats.avg_favorites * '3'::numeric)) + (COALESCE(engagement_stats.avg_rating, '0'::numeric) * '10'::numeric)) + ((engagement_stats.unique_builders)::numeric * 1.5)), 2))" " Buffers: shared hit=1348 read=125" " -> Sort (cost=10242.59..10319.24 rows=30660 width=144) (actual time=125.399..125.403 rows=3.00 loops=1)" " Output: engagement_stats.price_tier, engagement_stats.builds_count, (round(engagement_stats.avg_favorites, 1)), (round(COALESCE(engagement_stats.avg_rating, '0'::numeric), 2)), engagement_stats.unique_builders, (round((((((engagement_stats.builds_count * 2))::numeric + (engagement_stats.avg_favorites * '3'::numeric)) + (COALESCE(engagement_stats.avg_rating, '0'::numeric) * '10'::numeric)) + ((engagement_stats.unique_builders)::numeric * 1.5)), 2))" " Sort Key: (round((((((engagement_stats.builds_count * 2))::numeric + (engagement_stats.avg_favorites * '3'::numeric)) + (COALESCE(engagement_stats.avg_rating, '0'::numeric) * '10'::numeric)) + ((engagement_stats.unique_builders)::numeric * 1.5)), 2)) DESC" " Sort Method: quicksort Memory: 25kB" " Buffers: shared hit=1348 read=125" " -> Subquery Scan on engagement_stats (cost=7315.89..9490.37 rows=30660 width=144) (actual time=118.820..125.394 rows=3.00 loops=1)" " Output: engagement_stats.price_tier, engagement_stats.builds_count, round(engagement_stats.avg_favorites, 1), round(COALESCE(engagement_stats.avg_rating, '0'::numeric), 2), engagement_stats.unique_builders, round((((((engagement_stats.builds_count * 2))::numeric + (engagement_stats.avg_favorites * '3'::numeric)) + (COALESCE(engagement_stats.avg_rating, '0'::numeric) * '10'::numeric)) + ((engagement_stats.unique_builders)::numeric * 1.5)), 2)" " Buffers: shared hit=1348 read=125" " -> GroupAggregate (cost=7315.89..8570.57 rows=30660 width=112) (actual time=118.816..125.384 rows=3.00 loops=1)" " Output: (CASE WHEN (b.total_price < '500'::numeric) THEN 'Budget'::text WHEN (b.total_price < '1000'::numeric) THEN 'Mid-Range'::text WHEN (b.total_price < '2000'::numeric) THEN 'High-End'::text ELSE 'Enthusiast'::text END), count(DISTINCT b.id), avg(COALESCE(f.favorites_count, '0'::bigint)), avg(rb.value), count(DISTINCT b.user_id)" " Group Key: (CASE WHEN (b.total_price < '500'::numeric) THEN 'Budget'::text WHEN (b.total_price < '1000'::numeric) THEN 'Mid-Range'::text WHEN (b.total_price < '2000'::numeric) THEN 'High-End'::text ELSE 'Enthusiast'::text END)" " Buffers: shared hit=1348 read=125" " -> Sort (cost=7315.89..7410.03 rows=37655 width=53) (actual time=106.073..109.284 rows=47343.00 loops=1)" " Output: (CASE WHEN (b.total_price < '500'::numeric) THEN 'Budget'::text WHEN (b.total_price < '1000'::numeric) THEN 'Mid-Range'::text WHEN (b.total_price < '2000'::numeric) THEN 'High-End'::text ELSE 'Enthusiast'::text END), b.id, f.favorites_count, rb.value, b.user_id" " Sort Key: (CASE WHEN (b.total_price < '500'::numeric) THEN 'Budget'::text WHEN (b.total_price < '1000'::numeric) THEN 'Mid-Range'::text WHEN (b.total_price < '2000'::numeric) THEN 'High-End'::text ELSE 'Enthusiast'::text END), b.id" " Sort Method: quicksort Memory: 3562kB" " Buffers: shared hit=1348 read=125" " -> Hash Left Join (cost=2465.79..4454.01 rows=37655 width=53) (actual time=9.387..44.093 rows=47343.00 loops=1)" " Output: CASE WHEN (b.total_price < '500'::numeric) THEN 'Budget'::text WHEN (b.total_price < '1000'::numeric) THEN 'Mid-Range'::text WHEN (b.total_price < '2000'::numeric) THEN 'High-End'::text ELSE 'Enthusiast'::text END, b.id, f.favorites_count, rb.value, b.user_id" " Inner Unique: true" " Hash Cond: (b.id = f.build_id)" " Buffers: shared hit=1348 read=125" " -> Hash Right Join (cost=2465.74..4072.69 rows=37655 width=19) (actual time=9.367..33.926 rows=47343.00 loops=1)" " Output: b.total_price, b.id, b.user_id, rb.value" " Inner Unique: true" " Hash Cond: (rb.build_id = b.id)" " Buffers: shared hit=1348 read=125" " -> Seq Scan on public.rating_build rb (cost=0.00..1373.04 rows=89104 width=9) (actual time=0.025..3.442 rows=89104.00 loops=1)" " Output: rb.build_id, rb.user_id, rb.value" " Buffers: shared hit=482" " -> Hash (cost=2069.55..2069.55 rows=31695 width=14) (actual time=9.328..9.328 rows=31774.00 loops=1)" " Output: b.total_price, b.id, b.user_id" " Buckets: 32768 Batches: 1 Memory Usage: 1746kB" " Buffers: shared hit=866 read=125" " -> Bitmap Heap Scan on public.build b (cost=650.89..2069.55 rows=31695 width=14) (actual time=2.199..6.497 rows=31774.00 loops=1)" " Output: b.total_price, b.id, b.user_id" " Recheck Cond: ((b.created_at >= (CURRENT_DATE - '6 mons'::interval)) AND b.is_approved)" " Heap Blocks: exact=864" " Buffers: shared hit=866 read=125" " -> Bitmap Index Scan on idx_build_approved_created_price (cost=0.00..642.96 rows=31695 width=0) (actual time=2.129..2.129 rows=31774.00 loops=1)" " Index Cond: (b.created_at >= (CURRENT_DATE - '6 mons'::interval))" " Index Searches: 2" " Buffers: shared hit=2 read=125" " -> Hash (cost=0.04..0.04 rows=1 width=12) (actual time=0.015..0.016 rows=0.00 loops=1)" " Output: f.favorites_count, f.build_id" " Buckets: 1024 Batches: 1 Memory Usage: 8kB" " -> Subquery Scan on f (cost=0.01..0.04 rows=1 width=12) (actual time=0.015..0.016 rows=0.00 loops=1)" " Output: f.favorites_count, f.build_id" " -> GroupAggregate (cost=0.01..0.03 rows=1 width=12) (actual time=0.014..0.015 rows=0.00 loops=1)" " Output: favorite_build.build_id, count(DISTINCT favorite_build.user_id)" " Group Key: favorite_build.build_id" " -> Sort (cost=0.01..0.02 rows=1 width=8) (actual time=0.013..0.014 rows=0.00 loops=1)" " Output: favorite_build.build_id, favorite_build.user_id" " Sort Key: favorite_build.build_id, favorite_build.user_id" " Sort Method: quicksort Memory: 25kB" " -> Seq Scan on public.favorite_build (cost=0.00..0.00 rows=1 width=8) (actual time=0.008..0.008 rows=0.00 loops=1)" " Output: favorite_build.build_id, favorite_build.user_id" "Planning:" " Buffers: shared hit=42 read=1" "Planning Time: 1.620 ms" "Execution Time: 125.603 ms"
Result: Modest improvement, but the index still helped out with unnecessary reads, avoiding 43226 unnecessary row reads during the initial filtering phase, reducing time from 129.409ms to 125.603ms.
