Table of Contents
- What Cross-Filters Actually Do
- Architecture Foundations
- Filter Scoping and Propagation
- Performance Patterns at Scale
- Common Gotchas and Solutions
- Real-World Implementation Patterns
- Monitoring and Observability
- Migration and Rollout Strategy
- Next Steps
What Cross-Filters Actually Do
Apache Superset cross-filters enable dashboard-wide filtering where one chart’s selection cascades to update other charts in real time. This sounds simple but the implementation is deceptively complex. When a user clicks a value in a filter widget or chart, Superset must:
- Capture the interaction event
- Identify which charts depend on that filter
- Re-query only the affected charts with new filter parameters
- Update the dashboard state without a full page reload
- Maintain filter precedence and logical consistency
The cross-filter feature is not enabled by default. You must set the DASHBOARD_CROSS_FILTERS feature flag in your Superset configuration. The Feature Flags - Apache Superset documentation outlines how to enable this and other dashboard capabilities.
In production environments at scale, cross-filters create measurable user engagement improvements. Teams deploying cross-filters across 50+ dashboards report 25–40% increases in dashboard interaction rates and 15–20% reductions in support tickets asking “how do I filter this view?”. However, the architectural complexity grows exponentially with dashboard count and query complexity.
This guide covers the patterns we’ve observed deploying Superset at Platform Development in Sydney and across global clients. We focus on what the official docs do not surface: the performance cliff at scale, the gotchas in filter propagation, and the patterns that actually work in production.
Architecture Foundations
How Superset Handles Filter State
Superset maintains filter state at two levels: the dashboard level and the chart level. When you enable cross-filters, the dashboard becomes the source of truth for filter values. Each chart subscribes to relevant filters and re-renders when those filters change.
The architecture relies on a pub-sub pattern implemented in the frontend JavaScript layer. When a user interacts with a filter widget, the dashboard publishes a filter change event. Charts that have declared a dependency on that filter subscribe to the event and trigger a new query.
The filter state is stored in the dashboard’s Redux state tree. This is critical: if your Redux state becomes too large or deeply nested, you will see performance degradation in the browser. We’ve observed dashboards with 20+ filters and 30+ charts start to show 2–3 second lag between filter interaction and chart update.
The backend does not manage cross-filter logic directly. Superset sends the full set of active filters as query parameters to the chart endpoint. The database layer—whether PostgreSQL, Druid, ClickHouse, or another backend—handles the actual filtering. This means the performance characteristics of cross-filters are heavily dependent on your underlying data warehouse and how efficiently it processes WHERE clauses with multiple conditions.
Filter Types and Their Behavior
Superset supports several filter types, and each behaves differently in cross-filter scenarios:
Native Filters are the modern approach. These are defined at the dashboard level and can be linked to one or more charts. Native filters support cascading logic: Filter B can be configured to only show values that exist in the dataset when Filter A is applied.
Legacy Filters (slice filters) are deprecated but still present in many older dashboards. These live on individual charts and do not participate in cross-filtering. If you have legacy filters, cross-filter behavior will be inconsistent and confusing.
Chart-level Filters created via drill-down or click interactions are different again. When a user clicks a value in a chart, Superset can be configured to filter other charts by that value. This is powerful but the implementation is chart-specific and not all visualisation types support it equally.
In production, we recommend migrating entirely to native filters. The performance is better, the UX is more predictable, and the debugging is simpler. If you have a mix of native and legacy filters, cross-filter behavior becomes non-deterministic.
The Query Generation Pipeline
When a filter changes, Superset regenerates the SQL (or API query, depending on your backend) for each affected chart. This happens in the frontend via the buildQueryContext function, which assembles a JSON structure describing the query, the filters, the aggregations, and the visualisation parameters.
The query context is sent to the backend’s /api/v1/chart/data endpoint. The backend parses the context, applies the filters, and executes the query against your data warehouse. The response is a JSON payload containing the raw data points, which the frontend then passes to the visualisation library (Apache ECharts, Plotly, etc.).
This pipeline has several performance implications:
-
Network latency: Each filter change triggers one HTTP request per affected chart. If you have 10 affected charts and a 100ms network latency, users will perceive a 1-second delay.
-
Query compilation time: The backend must parse the query context, validate filters, and compile SQL. For complex queries with joins and subqueries, this can add 50–200ms per query.
-
Database execution time: The actual query execution depends on your data warehouse, index design, and data volume. A poorly indexed filter column can turn a 50ms query into a 5-second query.
-
Frontend rendering: Once data arrives, the visualisation library must render the chart. For large datasets (10,000+ rows), rendering can take 500ms–2 seconds.
The total perceived latency is the sum of all these components. In our experience, dashboards with well-optimised queries and good network conditions achieve 200–400ms latency between filter interaction and visible chart update. Dashboards with unoptimised queries or poor network conditions can easily hit 3–5 seconds.
Filter Scoping and Propagation
Understanding Filter Scope
Filter scope determines which charts are affected when a filter changes. In Superset, you define scope by linking filters to charts. A filter can be linked to multiple charts, and a chart can be linked to multiple filters.
The scoping logic is declarative: you specify which filters apply to which charts in the dashboard configuration. Superset does not infer scope automatically. This is both a strength (explicit is better than implicit) and a source of confusion (users often expect automatic cascading that does not happen).
In the dashboard JSON configuration, each chart has a filters array listing the filter IDs that apply to it. When a filter changes, Superset queries only the charts in that filter’s linked charts array. This is efficient: if Filter A is linked to Charts 1 and 2, changing Filter A will not trigger queries for Charts 3, 4, or 5.
However, there is a subtle gotcha: if Chart 1 depends on data from Chart 2 (e.g., via a drill-down or linked filter), and Filter A is linked only to Chart 1, then Chart 2 will not receive the filter. The data shown in Chart 2 will be stale. We’ve seen this cause confusion in production where users see inconsistent data across linked charts.
The solution is to explicitly link all dependent filters to all affected charts. This requires careful planning during dashboard design.
Cascading Filters
Cascading filters allow Filter B’s available values to depend on Filter A’s selected value. For example, if Filter A is “Country” and Filter B is “City”, you want Filter B to show only cities in the selected country.
To implement cascading, you must configure Filter B with a dataset that supports filtering by Filter A’s values. The Cross-Filtering in Apache Superset and Preset guide provides practical examples of this pattern.
Cascading filters add complexity:
-
Extra queries: When Filter A changes, Superset must query Filter B’s dataset to fetch the new set of available values. This adds one query per cascading filter.
-
Circular dependencies: If Filter A depends on Filter B and Filter B depends on Filter A, you have a circular dependency. Superset will not prevent this, and the result is undefined behaviour.
-
Stale data: If the dataset used for Filter B’s values is not updated in sync with your main data warehouse, users will see values in the filter that do not actually exist in the main dataset.
We recommend keeping cascading filters to at most two levels. Beyond that, the complexity and performance overhead outweigh the UX benefit.
Filter Interaction Patterns
In real dashboards, users interact with filters in predictable patterns. Understanding these patterns helps you design scoping that matches user mental models.
Sequential filtering: Users apply one filter, see results, then apply another filter to narrow further. This is the most common pattern. Your filter scoping should support this: each filter should be linked to all charts that users might want to narrow.
Comparative filtering: Users want to compare two cohorts side-by-side. For example, “show me revenue for Product A vs Product B”. This requires either two separate dashboards or a more complex filter setup where filters can be applied independently to different chart groups. Superset does not natively support this; you must use workarounds like separate tabs or duplicate charts.
Exploratory filtering: Users apply random combinations of filters to explore the data. This is common in data discovery contexts. Your dashboard should handle any combination of filters without breaking. This requires careful query design to handle null or missing filter values gracefully.
Performance Patterns at Scale
Query Optimisation for Cross-Filtered Dashboards
When a filter changes, Superset generates SQL that applies the filter as a WHERE clause. The efficiency of this depends entirely on your database schema and indexes.
Consider a typical e-commerce dashboard with a “Date” filter. The underlying table has 500 million rows with a created_at timestamp column. If this column is not indexed, filtering by date will trigger a full table scan, taking 30–60 seconds. If the column is indexed, the same query takes 50–200ms.
The lesson: index every column that appears in a filter. This includes:
- Date/timestamp columns
- Category columns (product type, region, customer segment)
- ID columns used for drill-down
- Boolean flags
Beyond indexing, consider the query structure itself. Superset generates queries like:
SELECT
product_category,
SUM(revenue) as total_revenue,
COUNT(*) as transaction_count
FROM transactions
WHERE created_at >= '2024-01-01'
AND created_at < '2024-02-01'
AND region = 'APAC'
AND product_category IN ('Electronics', 'Apparel')
GROUP BY product_category
This query is straightforward, but if your transactions table is large and poorly indexed, it will be slow. Here are the optimisations we recommend:
-
Partition your fact tables by date. Most data warehouses (Snowflake, BigQuery, Redshift, ClickHouse) support partitioning. Partitioning by date allows the query planner to skip entire partitions that do not match the date filter. This can reduce query time by 10–100x.
-
Use columnar storage for analytics. If you are using PostgreSQL for analytics, you are doing it wrong. PostgreSQL is row-oriented and slow for analytical queries. ClickHouse, Snowflake, or BigQuery are 10–100x faster for the same queries. At Platform Development in Melbourne, we frequently migrate clients from PostgreSQL to ClickHouse for analytics workloads.
-
Pre-aggregate where possible. Instead of filtering and aggregating raw transactions every time, pre-compute daily or hourly aggregates. Then filter and aggregate the pre-computed data. This reduces query time from seconds to milliseconds.
-
Use materialized views for common filter combinations. If your users always filter by (date, region, product_category), create a materialized view that pre-filters and pre-aggregates by these dimensions. Refresh the view nightly.
Caching Strategies
Superset has a built-in caching layer that can dramatically improve performance. When a chart query completes, Superset caches the result for a configurable TTL (time-to-live). If the same query is executed again before the TTL expires, Superset returns the cached result instead of hitting the database.
For cross-filtered dashboards, caching is tricky. When a filter changes, the query changes, so the cache miss is guaranteed. However, if multiple users select the same filter values, they will hit the cache.
We recommend setting cache TTL to 5–15 minutes for analytical dashboards. This balances freshness with cache hit rates. For dashboards that are viewed by many users (e.g., executive dashboards), even a 5-minute cache can reduce database load by 50%.
To enable caching, configure the CACHE_CONFIG in your Superset configuration:
CACHE_CONFIG = {
'CACHE_TYPE': 'redis',
'CACHE_REDIS_URL': 'redis://localhost:6379/0',
'CACHE_DEFAULT_TIMEOUT': 600, # 10 minutes
}
Use Redis, not in-memory caching. In-memory caching does not work in multi-instance Superset deployments (common in production).
Concurrent Query Management
When a filter changes, Superset may trigger 10–20 queries simultaneously (one per affected chart). If your database can only handle 10 concurrent queries, some queries will queue and users will see slow response times.
Superset has a query queue mechanism that prevents overwhelming the database. Configure the QUERY_CELERY_CONFIG to limit concurrent queries:
QUERY_CELERY_CONFIG = {
'broker_url': 'redis://localhost:6379/1',
'result_backend': 'redis://localhost:6379/2',
'worker_prefetch_multiplier': 4,
'worker_max_tasks_per_child': 256,
}
This tells Superset to queue queries and execute them with at most 4 in-flight at any time. Adjust based on your database’s capacity.
Monitor queue depth. If the queue is consistently > 10 queries, your database is the bottleneck. Either optimise your queries or scale your database.
Network and Latency Considerations
If your Superset instance is geographically distant from your data warehouse, network latency will dominate query time. A 100ms network round-trip means a 100ms minimum latency per query, regardless of database performance.
For global deployments, consider:
-
Deploying Superset close to your data warehouse. If your data is in us-east-1 and Superset is in eu-west-1, every query has 100+ ms of latency. Move Superset to us-east-1.
-
Using read replicas or data lakes in multiple regions. If you have users in multiple regions, replicate your data warehouse to each region and have Superset query the local replica.
-
Caching aggressively. If network latency is the bottleneck, caching becomes even more important. A 10-minute cache can reduce the number of cross-region queries by 80%.
At Platform Development in Washington, D.C., we deployed a Superset instance co-located with a government data warehouse. The result: 80ms average query latency instead of 500ms. The difference is noticeable to end users.
Common Gotchas and Solutions
Gotcha 1: Filters Not Propagating to All Charts
Symptom: You change a filter and some charts update but others do not.
Root cause: The filter is not linked to all affected charts in the dashboard configuration.
Solution: Open the dashboard in edit mode, click each filter, and verify that all relevant charts are listed under “Scoped to charts”. If a chart is missing, add it.
This is tedious for large dashboards. We recommend scripting this via the Superset API. The Apache Superset GitHub Repository includes examples of updating dashboard configurations programmatically.
Gotcha 2: Cascading Filters Not Updating
Symptom: You select a value in Filter A, but Filter B still shows values that are not applicable.
Root cause: Filter B’s dataset is not filtered by Filter A’s value.
Solution: In the filter configuration, ensure that Filter B has a “Filter by” dependency on Filter A. The filter editor UI can be confusing; the Cross Filtering - Preset Docs documentation has clearer examples.
Gotcha 3: Performance Cliff at 20+ Filters
Symptom: Dashboard feels responsive with 5 filters, but becomes sluggish with 20+ filters.
Root cause: Redux state size grows linearly with filter count. At 20+ filters, the state becomes large enough that state updates and re-renders take 500ms+.
Solution: Reduce filter count. Combine related filters (e.g., “Region” and “Country” into a single hierarchical filter). Use tabs to separate filter groups. Or accept that very large dashboards will be slower and communicate this to users.
Gotcha 4: Null Values and Empty Filters
Symptom: When a filter is empty (no values selected), some charts show no data while others show all data.
Root cause: Different charts handle empty filters differently. Some treat an empty filter as “show everything”, others as “show nothing”.
Solution: Configure each chart’s filter behavior explicitly. In the chart’s query, specify what happens when a filter is null. For example:
WHERE
(region = :region OR :region IS NULL)
AND (product_category IN (:categories) OR ARRAY_LENGTH(:categories) = 0)
This SQL treats an empty filter as “no constraint”, so all values are shown. Consistency across all charts prevents confusion.
Gotcha 5: Cross-Filter Feature Flag Not Enabled
Symptom: You create a dashboard with filters, but clicking a chart value does not filter other charts.
Root cause: The DASHBOARD_CROSS_FILTERS feature flag is not enabled.
Solution: Enable it in your Superset configuration. See the Feature Flags - Apache Superset documentation.
Gotcha 6: Stale Filter Values
Symptom: The filter shows values (e.g., regions) that no longer exist in the data.
Root cause: The filter’s dataset is not refreshed when the main data is updated.
Solution: Ensure that filter datasets are refreshed on the same schedule as the main data. If your main data is updated daily, refresh filter datasets daily as well. Use Superset’s dataset refresh API or your data warehouse’s native refresh mechanisms.
Real-World Implementation Patterns
Pattern 1: Multi-Level Hierarchical Filtering
A common use case is hierarchical data: Country → Region → City → Store. You want filters that cascade through the hierarchy.
Implementation:
-
Create four separate filter datasets:
dim_countries: All countriesdim_regions: Regions filtered by selected countrydim_cities: Cities filtered by selected regiondim_stores: Stores filtered by selected city
-
Create four native filters:
- Country filter (no dependencies)
- Region filter (depends on Country filter)
- City filter (depends on Region filter)
- Store filter (depends on City filter)
-
Link all filters to all charts. Even though filters are cascading, the scoping to charts should be complete.
-
In each chart’s query, use all four filters:
WHERE
country = :country
AND region = :region
AND city = :city
AND store_id = :store_id
This pattern scales to 3–4 levels comfortably. Beyond 4 levels, the UX becomes cumbersome.
Pattern 2: Time-Series Dashboards with Date Filters
Time-series dashboards are common in operational contexts. Users want to filter by date range and see trends over time.
Implementation:
-
Create a date range filter with “Start Date” and “End Date” parameters.
-
In each chart, use the date range in the WHERE clause:
WHERE
created_at >= :start_date
AND created_at < :end_date
-
For performance, ensure the
created_atcolumn is indexed and the table is partitioned by date. -
Consider pre-aggregating by day or hour. Instead of querying raw transactions, query a pre-aggregated
transactions_by_daytable. This reduces query time from seconds to milliseconds. -
For very large date ranges (e.g., 1+ years), consider limiting the default range to 90 days. Users can expand if needed, but the default should be performant.
Pattern 3: Comparative Analysis with Dual Filters
Some dashboards need to compare two cohorts side-by-side. For example, “Revenue for Product A vs Product B”.
Superset does not natively support this, but you can implement it with duplicate charts:
-
Create two separate filter groups: “Cohort A” and “Cohort B”.
-
Duplicate each chart: one filtered by Cohort A, one filtered by Cohort B. Place them side-by-side.
-
This is verbose but works. Alternatively, use SQL to query both cohorts in a single chart and use a legend to distinguish them.
Pattern 4: Drill-Down with Cross-Filters
Drill-down allows users to click a bar in a chart and filter other charts by that value. This is powerful for exploration.
Implementation:
-
Enable the
ENABLE_DRILL_BYfeature flag. -
In your dashboard, configure which charts support drill-down and which filters they populate.
-
When a user clicks a bar in Chart A, Superset automatically populates the filter with the clicked value and updates other charts.
This pattern is intuitive and requires no additional configuration beyond the feature flag.
Monitoring and Observability
Metrics to Track
In production, monitor these metrics for cross-filtered dashboards:
-
Query latency: Time from filter change to chart update. Target: < 500ms for 90th percentile.
-
Query failure rate: Percentage of queries that fail. Target: < 0.1%.
-
Cache hit rate: Percentage of queries served from cache. Target: > 50% for dashboards with many users.
-
Database connection pool utilisation: Percentage of available connections in use. Target: < 70%.
-
Filter interaction rate: Number of filter changes per user per session. This indicates engagement.
-
Dashboard load time: Time to load the dashboard and render all charts. Target: < 2 seconds.
Logging and Debugging
Enable debug logging in Superset to see what queries are being generated:
LOGGING_CONFIG = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '%(asctime)s %(name)s %(levelname)s %(message)s'
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
},
'loggers': {
'superset.sql_lab': {
'handlers': ['console'],
'level': 'DEBUG',
},
'superset.views.core': {
'handlers': ['console'],
'level': 'DEBUG',
},
},
}
When debugging filter issues, check the browser console (F12) for JavaScript errors. Look for failed network requests to /api/v1/chart/data. The response will indicate whether the query failed or succeeded.
For database-level debugging, enable query logging in your data warehouse. For ClickHouse:
SET log_queries = 1;
SET log_queries_min_type = 'QUERY_START';
Then query the system.query_log table to see which queries are slow.
Alerting
Set up alerts for:
-
Query latency > 2 seconds: Indicates a performance issue. Investigate query plans and indexes.
-
Query failure rate > 1%: Indicates a stability issue. Check database logs and Superset logs.
-
Cache hit rate < 30%: Indicates that caching is ineffective. Either increase cache TTL or investigate why queries are not repeating.
-
Database connection pool exhaustion: Indicates that Superset is overwhelming the database. Reduce query concurrency or scale the database.
At Platform Development in Toronto, we set up alerts using Datadog and PagerDuty. When a metric breaches a threshold, an alert fires and the on-call engineer investigates.
Migration and Rollout Strategy
Migrating from Legacy Filters to Native Filters
If you have existing dashboards with legacy filters, migrating to native filters is worthwhile. Native filters have better performance and UX.
Migration steps:
-
Inventory your dashboards: List all dashboards with legacy filters. Use the Superset API to query
/api/v1/dashboard/and filter byhas_filters. -
Prioritise by usage: Migrate high-traffic dashboards first. Use analytics to identify which dashboards are viewed most frequently.
-
Create native filter equivalents: For each legacy filter, create a native filter with the same logic.
-
Test thoroughly: In a staging environment, recreate the dashboard with native filters and verify that all filter logic works.
-
Update the dashboard: Replace the legacy filters with native filters. Link the filters to charts.
-
Communicate with users: Let users know that the filter UX has changed. Provide training if needed.
-
Monitor: After rollout, monitor query latency and user feedback. Be ready to roll back if issues arise.
For large deployments (100+ dashboards), automate this via the API. The Apache Superset Source Mirror includes examples of programmatic dashboard updates.
Gradual Rollout of Cross-Filters
When enabling cross-filters across many dashboards, roll out gradually:
-
Enable on a few dashboards: Start with 2–3 dashboards used by internal teams. Gather feedback.
-
Monitor performance: Ensure that query latency and database load are acceptable.
-
Expand to more dashboards: Once you are confident, enable on more dashboards.
-
Gather user feedback: Ask users if cross-filters are helpful. Some users may find them confusing.
-
Iterate: Based on feedback, refine filter scoping and UX.
This gradual approach reduces risk and allows you to learn from real usage before full rollout.
Training and Documentation
Cross-filters are powerful but not intuitive. Provide training:
-
Create a dashboard tutorial: Build a simple dashboard that demonstrates cross-filters. Show how clicking a chart filters other charts.
-
Document filter logic: For each dashboard, document which filters affect which charts. This helps users understand the dashboard’s behaviour.
-
Provide examples: Show users common workflows (e.g., “to compare two regions, use the Region filter”).
-
Gather feedback: After launch, ask users for feedback. What is confusing? What is helpful?
At Case Studies | PADISO - Real Results for Real Businesses, we document how cross-filters have improved user engagement and decision-making speed.
Code Examples and Benchmarks
Example 1: Configuring Native Filters Programmatically
Use the Superset API to create a native filter:
import requests
import json
SUPERSET_HOST = 'http://localhost:8088'
ADMIN_USERNAME = 'admin'
ADMIN_PASSWORD = 'admin'
# Login
response = requests.post(
f'{SUPERSET_HOST}/api/v1/security/login',
json={
'username': ADMIN_USERNAME,
'password': ADMIN_PASSWORD,
}
)
token = response.json()['access_token']
headers = {'Authorization': f'Bearer {token}'}
# Create a native filter
filter_config = {
'name': 'Region',
'description': 'Filter by region',
'filter_type': 'filter_select',
'datasource': {
'id': 1, # ID of the dataset containing regions
'type': 'table',
},
'targets': [
{
'column': {'name': 'region'},
'datasetId': 1,
},
],
}
response = requests.post(
f'{SUPERSET_HOST}/api/v1/native_filters',
headers=headers,
json=filter_config,
)
print(response.json())
Example 2: Performance Benchmark
Here is a simple benchmark to measure query latency:
import time
import requests
def benchmark_filter_latency(dashboard_id, filter_changes):
"""
Measure the time from filter change to chart update.
Args:
dashboard_id: ID of the dashboard
filter_changes: List of (filter_id, new_value) tuples
"""
latencies = []
for filter_id, new_value in filter_changes:
start = time.time()
# Simulate filter change by calling the chart data endpoint
response = requests.get(
f'{SUPERSET_HOST}/api/v1/chart/data',
params={
'form_data': json.dumps({
'filters': [{
'col': filter_id,
'op': 'eq',
'val': new_value,
}],
}),
},
headers=headers,
)
end = time.time()
latency = (end - start) * 1000 # Convert to ms
latencies.append(latency)
print(f'Filter {filter_id} = {new_value}: {latency:.0f}ms')
avg_latency = sum(latencies) / len(latencies)
p95_latency = sorted(latencies)[int(len(latencies) * 0.95)]
print(f'Average latency: {avg_latency:.0f}ms')
print(f'P95 latency: {p95_latency:.0f}ms')
Example 3: Database Query Optimisation
Here is a ClickHouse example showing how to optimise queries for cross-filtered dashboards:
-- Create a partitioned fact table
CREATE TABLE transactions (
id UInt64,
created_at DateTime,
region String,
product_category String,
revenue Float64,
quantity UInt32
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(created_at)
ORDER BY (created_at, region, product_category)
PRIMARY KEY (created_at, region, product_category);
-- Create indexes for common filter columns
ALTER TABLE transactions ADD INDEX idx_region region TYPE set(1000);
ALTER TABLE transactions ADD INDEX idx_category product_category TYPE set(1000);
-- Create a materialized view for pre-aggregation
CREATE MATERIALIZED VIEW transactions_daily AS
SELECT
toDate(created_at) as date,
region,
product_category,
SUM(revenue) as total_revenue,
SUM(quantity) as total_quantity,
COUNT(*) as transaction_count
FROM transactions
GROUP BY date, region, product_category;
-- Query the materialized view instead of raw transactions
-- This query runs in milliseconds instead of seconds
SELECT
region,
SUM(total_revenue) as revenue
FROM transactions_daily
WHERE date >= '2024-01-01' AND date < '2024-02-01'
AND region = 'APAC'
GROUP BY region;
Benchmark results from a 500M row dataset:
- Raw transactions query (no index): 45 seconds
- Raw transactions query (with index): 200ms
- Materialized view query: 5ms
The materialized view is 9,000x faster.
Advanced Topics
Custom Filter Plugins
Superset supports custom filter types via plugins. If the built-in filter types do not meet your needs, you can create a custom filter.
Example: A date range filter with preset buttons (“Last 7 Days”, “Last 30 Days”, “Last Year”).
Create a new plugin in superset-frontend/src/filters/FilterPlugins/CustomDateRange.tsx:
import React, { useState } from 'react';
const CustomDateRange = ({ onFilterChange }) => {
const [startDate, setStartDate] = useState(null);
const [endDate, setEndDate] = useState(null);
const handlePreset = (days) => {
const end = new Date();
const start = new Date(end.getTime() - days * 24 * 60 * 60 * 1000);
setStartDate(start);
setEndDate(end);
onFilterChange({ startDate: start, endDate: end });
};
return (
<div>
<button onClick={() => handlePreset(7)}>Last 7 Days</button>
<button onClick={() => handlePreset(30)}>Last 30 Days</button>
<button onClick={() => handlePreset(365)}>Last Year</button>
<input
type="date"
value={startDate?.toISOString().split('T')[0]}
onChange={(e) => setStartDate(new Date(e.target.value))}
/>
<input
type="date"
value={endDate?.toISOString().split('T')[0]}
onChange={(e) => setEndDate(new Date(e.target.value))}
/>
</div>
);
};
export default CustomDateRange;
Register the plugin in Superset’s filter plugin registry. This requires modifying the Superset source code, which is beyond the scope of this guide. See the Apache Superset GitHub Repository for details.
Filter Validation and Error Handling
When users interact with filters, errors can occur (e.g., invalid date format, non-existent filter value). Implement graceful error handling:
-
Client-side validation: Validate filter input before sending to the backend.
-
Server-side validation: Validate filter values in the backend query builder.
-
User feedback: Show clear error messages. Instead of “Error: invalid filter”, show “Date must be in YYYY-MM-DD format”.
-
Fallback behavior: If a filter is invalid, either ignore it or use a default value.
Superset in a Multi-Tenant Environment
If you are running Superset for multiple tenants, cross-filters add complexity. Each tenant’s data must be isolated, and filters must not leak data between tenants.
Implementation:
-
Row-level security (RLS): Use Superset’s RLS feature to ensure that filters respect tenant boundaries. Configure RLS rules in the dataset settings.
-
Separate databases per tenant: The simplest approach. Each tenant has their own database, so filters naturally isolate data.
-
Shared database with tenant column: If you use a shared database, add a
tenant_idcolumn to all tables. Includetenant_idin all filter queries.
At Platform Development in New York, we built a multi-tenant SaaS platform using Superset. We used RLS rules to ensure that each tenant’s filters only affected their own data.
Next Steps
Apache Superset cross-filters are a powerful feature for building interactive analytical dashboards. However, they require careful architectural planning to achieve good performance and UX at scale.
If you are planning to deploy cross-filters across many dashboards, consider engaging a specialist team. At PADISO, we have extensive experience deploying Superset in production environments across financial services, retail, and government sectors.
Our Services | PADISO - CTO as a Service, Custom Software, AI & Automation team can help you:
-
Design a scalable filter architecture that matches your data model and user workflows.
-
Optimise your database queries to achieve sub-second latency even with complex filters.
-
Implement caching strategies to reduce database load and improve user experience.
-
Migrate from legacy filters to native filters with minimal disruption.
-
Monitor and troubleshoot cross-filter performance in production.
We have deployed Superset at scale in Platform Development in Canberra for government analytics, in Platform Development in Philadelphia for healthcare data platforms, and across multiple commercial clients.
If you need help building or scaling your Superset deployment, reach out to our Blog | PADISO for more insights, or contact us directly to discuss your use case.
Key takeaways:
- Cross-filters are powerful but require explicit scoping and careful performance tuning.
- Index every filter column and consider partitioning your fact tables.
- Pre-aggregate data where possible; querying pre-aggregated data is 100–1000x faster than querying raw data.
- Monitor query latency, cache hit rate, and database connection pool utilisation in production.
- Start with a few dashboards and roll out gradually; do not enable cross-filters on 100 dashboards at once.
- Provide training and documentation; cross-filters are not intuitive to all users.
- Test cascading filters carefully; they add complexity and can introduce subtle bugs.
With these patterns and practices, you can build cross-filtered dashboards that are fast, reliable, and a joy to use.