Session specific caching and user specific caching

Caching in Streamlit

Goal -

1. Caching per session - Here we should be able to cache functions per session of a user. Here the user is not unique so the cache will be limited only to that particular session and will not be stored or preserved on the server. Modifications to data or files by the user will be limited to that session only and will not be applied globally.

2. Caching per user globally - Here the user is unique and is identified by the server, so here cache should be applied to that user across all the sessions. However, if the user changes then the cache of other users will not be applied or affected by the activities of a particular user. Modifications to data /files by the user should be preserved on the server for that user only.

Please guide me to achieve these goals in a streamlit app.

Did you find any solution? I’m also trying to cache data across multiple sessions…

st.cache, st.experimental_memo and st.experimental_singleton all cache data across multiple sessions. If you don’t know how to use them, start here:

Possible solution for Session-caching

1. Concept -
St.experimental_singleton caches a function for the same set of arguments across all sessions of a streamlit app. To limit it to a particular session there is a need for a unique argument for each session. Here comes a secret token handy that is hashable, unlike a UUID. This secret makes a function signature unique to each session for the same set of arguments.
This secret token will be generated once per session and stored in a session state to preserve it for reruns on any action like the click of a button.

2. Approach -
To cache a function the @st.experimental_singleton decorator is used and an additional parameter is added to the function definition.
The additional parameter is a secret token generated using the secret module of python. The token is generated once per session and stored in a session state. Further, we check for each rerun if the secret token is present in a session state. If the secret is not in the session state we generate a new secret and store it in a session state. If the token is present we pass it as an argument to each of the cached functions hence caching is achieved for each unique session/secret.

3. Scope for Improvement -
Configure a way to clear the session state to reduce memory usage across all the sessions.
Configure a way to clear all the cache of previous sessions for the same reason as above.

4. Code -

import secrets
uu = "session_id"
if uu in st.session_state:
    uu = st.session_state[uu]
    new = secrets.token_urlsafe(16)
    st.session_state[uu] = new

@Shneor Please let me know if this works for you this is just a solution for the first part and I am working on the second part.
If you find solution for the second part please let share it.

@Goyo I am currently using st.session_state but as the name suggests, it clears all the data as soon as you close the tab or even refresh the website, then there is st.cache which is a decorator for functions.
Is there a way to save key/value pairs to st. experimental_memo or st.experimental_singleton?
cause I tried with both and I wasn’t able to save any key/value, it seems like I could only use it as a decorator for a function

(I’m trying to save all the queries a user has executed in a list, so you could always look back at your history)

A collection of key/value pairs is a dict, and “saving” suggest that you want the changes to the dict to be preserved across sessions, which requires experimental_singleton. So just write a function that returns an empty dictionary and decorate it with @st.experimental_singleton, then use the return value as you please and the changes will be preserved across sessions. Each time you call the function the same object will be returned, including whatever changes were made to it.

There is no concept of a user in streamlit apps, so you will have to figure out how you want to do that part. Assuming you have a variable user with an appropriate value, you can make it work pretty much the same way. Write a function that takes the user as a parameter and returns an empty list and decorate it with @st.experimental_singleton. Now each time you access the dictionary you want to insert an element to the list, write functions that do both things and access the dictionary only through those functions.

Thanks for the quick reply!
Youre solution is genius, I didn’t even think of this that we could just cache the function and mutate the result which is store somewhere in cache, now I have one problem, that the cache is accessible from all devices since its not user-specific. In fact you go to the Query History on the website you will see the queries I just did.

Now there still are st.experimental_memo and st.experimental_user, are you familiar with those two? and if so would any of these solve the issue ?

There are some hints in the docs, like this

Singleton objects can be used concurrently by every user connected to your app, and you are responsible for ensuring that @st.singleton objects are thread-safe . (Most objects you’d want to stick inside an @st.singleton annotation are probably already safe—but you should verify this.)

Or this

Each singleton object is shared across all users connected to the app. Singleton objects must be thread-safe, because they can be accessed from multiple threads concurrently.

Thread safety is only a concern when you mutate shared state, inmutable (or inmutated, if that is a word) state is always safe.

BTW, be aware of thread safety if you do this. The key-value pair you just wrote might have a different value or not be there at all the next moment because another session changed the shared state.

That should be ok, the same user can use several devices. If you want to keep user-specific state, I can think of two ways of doing it:

  1. Making the shared state a dictionary where each key is a user id and the value is the state related to that user.

  2. Making the function that return the state take an user id as parameter, so that it returns a different object for each user.

Again, whatever you do, you need to add the concept of a user to your application.

st.experimental_memo doesn’t cache the return value itself but a serializad version of it

Unlike @st.cache, this returns cached items by value, not by reference. This means that you no longer have to worry about accidentally mutating the items stored in the cache. Behind the scenes, this is done by using Python’s pickle() function to serialize/deserialize cached values.

Si it can’t work for shared mutable state because each time you call the function you get a fresh copy of the original object and the cache is not aware of any changes you make to your copy.

st.experimental_user can be a way to identify the user in very specific cases. Take a look at the docs, in particular how it behaves in different contexts to see if it can be of any use to you.

1 Like