Slow dashboard tiles? Check your filters!
Have you ever opened a Looker dashboard with ten filters, watched the tiles spin for longer your expected, and wondered why it’s taking so long when the underlying database is usually fast?
The culprit is often a silent connection storm with the tiles AND the dashboard filters. By default, Looker issues separate, concurrent database queries for every type: string filter on a dashboard the moment it loads, just to populate the suggestion dropdowns. It is the database equivalent of a restaurant kitchen prepping every single menu item the second a customer sits down, before they have even opened the menu.
Before trying to optimize these queries, ask yourself: do your users actually need dynamic suggestions for all of these filters? If a field has high cardinality (like a user_uuid) or if the values are static and change only once a year, you should not query the database for suggestions.
Let's look at why this happens and how to manage suggestions in LookML to protect your database connection pool.
The Hook: Why Suggestion Queries Starve Dashboard Tiles
Looker manages database connections using a connection pool. This pool is divided into connections for executing queries (both dashboard tiles and filter suggestions) and a smaller reserve dedicated to application tasks.
This connection pooling is a critical safety feature. Without it, Looker would open a new database connection for every single query and filter suggestion on every user load. Under heavy dashboard traffic, this would quickly exhaust your database’s connection limits, causing connection drops or outright database crashes. By capping and queueing these requests in Looker, your database remains stable and protected under spikes in load.
Think of this connection pool like a busy coffee shop with a limited number of baristas. Most baristas are busy taking orders and steaming milk (for both dashboard tiles and suggestions), while the shift supervisor stands by strictly to handle refunds or order cancellations.
If a dashboard loads with ten filters, it is the equivalent of ten customers rushing the counter all at once just to ask the baristas for a small sample of oat milk (the filter suggestions). If all baristas are busy handing out samples, a customer waiting to order a double espresso (a dashboard tile query) is forced to wait in a queue, stalling the coffee shop's service.
When a dashboard loads:
- Each filter with suggestions enabled dispatches a concurrent SQL query to fetch unique values.
- If a dashboard has ten filters, it attempts to open ten concurrent database connections.
- If the number of filters exceeds the available database connections, Looker experiences thread pool contention.
When contention occurs, Looker queues the remaining queries. Because suggestion queries are often fast and simple, they can saturate the connection pool and "starve" the larger, more complex queries needed for the dashboard tiles. Your dashboard tiles are forced to wait in line until the suggestion queries complete, dragging down the overall dashboard load time.
This queueing creates a frustrating user experience. A user lands on the dashboard, sees the tiles spinning endlessly, assumes Looker is slow, and hits the refresh button. Refreshing cancels the active queries but immediately spawns a brand-new batch of suggestion and tile queries. This vicious cycle compounds the connection contention, extending the load times even further and dragging down performance for other users sharing the same database connection.
Adjusting database connection limits in Looker requires administrator privileges. If you frequently see queries queueing on dashboard load, coordinate with a Looker admin to evaluate your max_connections settings.
UI Control Types: Initial Load vs. Subsequent Typing
A common misconception is that changing a dashboard filter's UI control type (e.g., from a Drop-down Menu to Checkboxes or a Button Group) prevents the database query on load. It does not.
Looker determines whether to query the database for suggestions based on the underlying field’s LookML configuration—specifically whether the field is suggestable (which is the default for string fields) and does not have hardcoded static suggestions.
Here is how different filter UI components behave:
On Dashboard Load
Regardless of the UI control type—whether it is a Drop-down Menu, Checkboxes, Button Group, Tag List, or Advanced filter—Looker will query the database on dashboard load to populate the suggestions. If a dashboard has ten filters of any control type, it will still attempt to fire ten concurrent suggestion queries on load.
On User Interaction (Typing)
The differences between UI controls only emerge once the dashboard has loaded and users begin interacting with the filters:
- Checkboxes, button groups, and radio buttons avoid triggering additional database queries during user interaction, displaying only the values loaded initially.
- Drop-down menus with search enabled, tag lists, and advanced filters send new search queries to the database as the user types.
LookML Strategies for Performant Suggestions
You can control suggestion behavior directly in LookML using field parameters. Here are the four primary strategies.
Disable Suggestions for High-Cardinality Fields
For dimensions containing unique identifiers, email addresses, or free-text comments, suggestions are not helpful and can be slow to run. Turn them off completely.
dimension: user_uuid {
type: string
sql: ${TABLE}.user_uuid ;;
suggestable: no
}
With suggestable: no, Looker will not run a query on load. Users can still filter by typing the exact value, but they won't see a dropdown.
Hardcode Static Values
If a field has a small, static set of values (such as order status, region, or department), provide them directly in LookML.
dimension: order_status {
type: string
sql: ${TABLE}.status ;;
suggestions: ["pending", "completed", "cancelled"]
}
This completely eliminates the suggestion database query, serving the values instantly from memory.
Automating Static Value Updates with AI Agents
While hardcoding values is great for dashboard performance, it introduces maintenance overhead. If a new status value is added once or twice a year, the static list in LookML will drift out of date.
To solve this, you can orchestrate an AI developer agent equipped with looker-open-source/looker-cli to automate updates:
- Run a scheduled script or task that queries the database via the CLI to fetch the latest active statuses:
looker-cli query runquery '{"model":"my_model","view":"orders","fields":["orders.status"]}' - Compare the output list against the current
suggestions: [...]array defined in your LookML view files. - If a discrepancy is detected, let the agent rewrite the dimension definition block to update the suggestions list.
- Commit the changes, run automated LookML tests, and push the branch to production.
Catching Suggestions Drift with LookML Data Tests
If you want to catch drift before an agent runs, or if you prefer a manual pull request process, you can write a native LookML data test to verify that database values match your hardcoded suggestions.
If the database returns a new status that is not in the list, the test fails, preventing deployment to production:
test: order_status_suggestions_are_valid {
explore_source: orders {
column: status { field: orders.status }
}
assert: status_matches_expected_suggestions {
expression: ${orders.status} = "pending"
or ${orders.status} = "completed"
or ${orders.status} = "cancelled" ;;
}
}
This hybrid approach gives you the performance benefits of zero-database-query static suggestions with the low maintenance of automated dynamic updates.
Redirect to a Smaller Lookup Table
If you need dynamic suggestions but the main table is large (e.g., billions of rows in an orders table), point Looker to a smaller, dedicated lookup table (e.g., a products table) to fetch the unique values.
dimension: product_brand {
type: string
sql: ${TABLE}.brand ;;
suggest_explore: product_lookup
suggest_dimension: product_lookup.brand_name
}
The suggestion query will run against the product_lookup explore instead of the large orders table, which reduces query execution time.
Accelerate Suggestions with Aggregate Awareness
Looker’s aggregate awareness automatically routes data queries to smaller, pre-aggregated tables to speed up dashboards. However, filter suggestions are dimension-only queries (SELECT DISTINCT), which means they do not trigger aggregate awareness automatically unless the aggregate table is configured to match.
To leverage aggregate tables directly for filter suggestions, define an aggregate table in your Explore that contains the dimension you want to suggest. If you are working with existing models, you can use LookML refinements to layer the aggregate table and dimension updates onto your project without modifying the baseline files:
# 1. Refine the existing Explore to add the aggregate table
explore: +order_items {
aggregate_table: category_suggestions {
query: {
dimensions: [products.category]
}
materialization: {
datagroup_trigger: orders_daily_datagroup
}
}
}
# 2. Refine the view to configure suggestions behavior on the dimension
view: +order_items {
dimension: category {
suggest_dimension: products.category
suggest_explore: order_items
}
}
By defining the aggregate table with the category dimension, any suggestion query for the category filter will be automatically intercepted by Looker and served from the pre-aggregated table, avoiding a costly scan on the raw, granular table.
Best Practices Checklist
- Review dashboards with more than five filters and identify fields that can use suggestable: no.
- Have a Looker administrator coordinate your database connection's max_connections setting with the number of suggestion-enabled filters on your dashboards to prevent thread pool contention.
- Hardcode suggestions using the suggestions parameter for low-cardinality fields that rarely change.
- Use suggest_dimension to redirect suggestion queries from massive transactional tables to small dimensional tables.
- Leverage aggregate awareness by defining an aggregate table with the target dimension in your Explore, and use full_suggestions: yes to ensure security filters like sql_always_where are preserved on suggestion queries.
- Prefer UI controls like Checkboxes or Button Groups over Drop-down Menus when feasible. While they still query the database once on load, they avoid sending additional query storms as the user interacts with the filter.