Streamlit for Energy Analysts: Keeping a Data-Heavy Dashboard Fast
Cache the data pulls, fragment the reruns, lazy-load the tabs — how to keep a Streamlit dashboard with years of hourly energy prices responsive.
Streamlit's execution model is beautifully simple — every interaction reruns the script top to bottom — and that simplicity becomes a liability the moment your dashboard pulls years of hourly prices, computes KPIs across time bands, and renders half a dozen charts. An energy analytics terminal I maintain hit exactly that wall. The fixes below are general; they apply to any data-heavy Streamlit app.
Cache the data, not the hope
The single biggest win: never fetch twice. Wrap every expensive operation — API pulls from sources like ENTSO-E, CSV parsing, heavy aggregations — in @st.cache_data with a sensible TTL, and put long-lived clients (database connections, API sessions) behind @st.cache_resource. A TTL of a few hours is usually right for energy market data: fresh enough for analysis, stable enough to keep the app snappy.
import streamlit as st
@st.cache_data(ttl=6 * 3600, show_spinner="Loading price history…")
def load_prices(start: str, end: str):
# expensive API pull + parsing happens once per (start, end)
return fetch_hourly_prices(start, end)
@st.cache_resource
def get_db():
# connection object shared across reruns, never pickled
return create_connection()One subtlety: cache_data hashes arguments, so pass plain strings and numbers, not DataFrames or custom objects, as parameters — otherwise every call looks new and the cache never hits.
Fragment the reruns
@st.fragment is the most underused tool in the Streamlit toolbox. Decorate a chart function with it and interacting with that chart's widgets reruns only the fragment, not the whole page. In practice this means: moving a date slider on the load-curve chart no longer recomputes the KPI row, the heatmap, and the export table. Structure the page as independent fragments and the app feels instant even when the total computation is heavy.
Keep filters in session state, compute once
Put filter widgets (date range, price zone, time band F1/F2/F3) in the sidebar, store their values in st.session_state, and derive every downstream dataset from a single filtered DataFrame computed once per rerun. The anti-pattern is filtering separately inside each chart function — same predicate evaluated five times, five chances to drift out of sync.
Lazy-load with tabs
Streamlit renders every tab's content on each rerun unless you guard it. Wrap each tab's body in a check of the active tab, or simply accept the cost for cheap tabs and isolate the expensive ones (Monte Carlo simulations, large heatmaps) behind explicit "Run" buttons. A simulation that takes 20 seconds is fine behind a button; it is infuriating when it retriggers because someone toggled an unrelated checkbox.
Keep the app warm
If you host on Streamlit Community Cloud, idle apps go to sleep and the first visitor waits through a full cold start — dependency install included. A lightweight scheduled ping every few hours keeps the instance warm. It is not elegant, but for a dashboard people open during market hours, the difference between a 2-second load and a 90-second cold start is the difference between a tool and a chore.
None of these techniques is exotic. Together they take a dashboard from "works on my machine with a small CSV" to "handles years of hourly data without making the analyst wait" — which is the actual job.