st.cache_data: ttl expiry or manual .clear() for data that updates on a schedule?
0 reputation · 25 Nov 2022, 10:06 UTC
0 reputation · 25 Nov 2022, 10:06 UTC
I have a Streamlit app (modern st.cache_data/st.cache_resource API) where a cached function reads a dataset that is refreshed by an external job at known times, roughly every few hours. The cache key (function name, arguments, code hash) does not change when the underlying data changes, so without intervention users keep seeing the stale result.
I'm weighing two documented approaches and can't decide which fits this constraint better. Option one is the ttl parameter, which bounds staleness but only guarantees eventual refresh — a user hitting the app just before expiry still sees data up to ttl seconds old, and a short ttl means paying recomputation cost even when nothing changed. Option two is calling the cached function's .clear() method (or st.cache_data.clear() globally) from an event-driven path, which gives exact freshness but requires the app to know when the external job ran, and a global clear would spike latency for every session on the next run.
Assume a recent Streamlit release; exact parameter names should be checked against the installed version's API reference.
Which approach is more appropriate when data updates on a predictable but externally-triggered schedule?
If I use .clear(), is there a documented pattern for triggering it from outside the app process, or must invalidation happen inside a Streamlit run?
Does max_entries eviction interact with either approach in a way that changes the trade-off?
30775 reputation · 25 Nov 2022, 21:04 UTC
For data that is refreshed at a fixed, externally‑triggered interval, using ttl on the cached function is usually the most appropriate choice. It lets Streamlit automatically invalidate the entry after the specified number of seconds, keeping the cache fresh without extra code. Manual .clear() is useful only when you need to force an immediate refresh outside the regular interval (e.g., an admin button or a webhook that notifies the app that the dataset has changed). The max_entries setting simply limits how many distinct cache keys can be stored; it does not interfere with TTL or manual clearing, but it can cause older keys to be evicted if the limit is reached.
ttl=3600. The cache will be considered stale after that period, and the next user request will trigger a recomputation automatically.ttl.@st.cache_data(ttl=3600)
def load_data():
# read from disk or database
return pd.read_csv("/path/to/file.csv")
# In the UI
if st.button("Refresh data"):
load_data.clear()
st.success("Cache cleared – next call will reload")
max_entries limits the total number of distinct cache keys. It works independently of ttl and .clear(). If you set max_entries=1 for a single function, the cache will never grow beyond that key, so TTL or clear will still govern when the value is recomputed. If you have many cached functions, a low max_entries may cause older functions to be evicted before their TTL expires, but this does not affect the freshness of the data for a given function.
ttl accordingly.max_entries high enough to avoid unintentional eviction of your cached function.Does the external job always finish at the same time, or can it finish earlier/later than the scheduled window? If the timing is highly variable, a manual .clear() triggered by a webhook from the job might be preferable.
Use comments to ask for clarification. Post a solution as an answer.
30,775 reputation · 25 Nov 2022, 17:49 UTC
A practical nuance for external invalidation is that .clear() must run inside a Streamlit execution context. You cannot call a cached function’s clear() from an external process directly; a typical pattern is to have the script read a signal written by the external job, such as a file mtime or a DB flag, and invalidate conditionally on the next run.
Also note the difference between func.clear() and st.cache_data.clear(). The former clears only that function’s entries, while the latter wipes all st.cache_data entries and can cause a thundering-herd recompute across sessions.
With max_entries, eviction is LRU, not TTL-aware. Under a low entry limit and many distinct arguments, a cached result can be evicted before its TTL expires, which changes the staleness bound you expect from TTL alone.