Streamlit ist der schnellste Weg, eine Python-Funktion in eine geteilte UI zu verwandeln. Die Grenzen erleben die meisten Teams nicht beim ersten Deployment, sondern sechs Monate später — wenn das Dashboard von zwanzig statt zwei Personen erwartet wird, die Datenquelle jetzt ein Produktions-Warehouse ist und der PM rollenbasiertes Filtern möchte, für das das Framework nicht ausgelegt war.
Dieser Artikel behandelt die vier Bereiche, in denen Prototyp-Annahmen zu Produktions-Problemen werden — und die Muster, die das ohne Rewrite beheben.
1. Caching: der Unterschied zwischen schnell und teuer
Streamlit führt Ihr Skript bei jeder Interaktion neu aus. Ohne Caching sendet jeder Klick, der pd.read_sql(...) auslöst, eine Anfrage an Ihr Warehouse. Bei wenig Traffic ist das unsichtbar. Bei zwanzig gleichzeitigen Nutzern werden daraus eine Warehouse-Rechnung und ein Latenz-Problem gleichzeitig.
st.cache_data ist für Funktionen, die Daten zurückgeben — DataFrames, Dicts, Listen. Es serialisiert den Rückgabewert und vergleicht ihn bei Cache-Hit. Verwenden Sie es für Warehouse-Queries, API-Calls und reine Transformationen, die nur von ihren Parametern abhängen.
st.cache_resource ist für Verbindungen und Objekte, die nicht serialisiert werden sollen — Datenbankverbindungen, Modell-Gewichte, thread-unsichere Clients. Der Rückgabewert wird Session-übergreifend geteilt. Diese Unterscheidung ist entscheidend: ein Connection-Pool, der mit st.cache_data gecacht wird, schlägt bei der Serialisierung still fehl; einer mit st.cache_resource wird korrekt geteilt.
TTL ist in Produktion Pflicht. @st.cache_data(ttl=300) weist Streamlit an, die Funktion nach fünf Minuten neu auszuführen. Ohne TTL lebt der Cache bis zum Neustart des Prozesses — Nutzer sehen möglicherweise stundealte Daten ohne Hinweis darauf.
Nach Eingangsparametern cachen. Streamlit cached anhand der Funktionsargumente. Wenn sich die Query mit einem nutzergewählten Datumsbereich ändert, ändert sich der gecachte Wert korrekt. Wenn der Datumsbereich in einer globalen Variable steckt und nichts übergeben wird, teilen alle Nutzer dasselbe Cache-Ergebnis unabhängig von ihrem Filter.
2. Session-State: Isolation zwischen Nutzern
st.session_state ist pro Session — das klingt sicher. Das Problem: Viele Streamlit-Apps akkumulieren State, der unbegrenzt wächst — angehängte Konversationsverläufe, wachsende DataFrames, nicht aufgeräumte temporäre Dateien. Eine Session, die acht Stunden läuft, verbraucht Speicher proportional zu jeder Interaktion. In einem geteilten Deployment mit zwanzig Nutzern führt das zum OOM-Kill.
Explizite Grenzen setzen: nur die letzten N Einträge in Konversationslisten behalten, große DataFrames bei Datenquellenänderung zurücksetzen, rohe File-Uploads nie länger im Session-State halten als den Render-Cycle, der sie braucht.
Ein zweites Muster: zwischen UI-State (welcher Filter gewählt ist) und abgeleitetem State (der gefilterte DataFrame) unterscheiden. Abgeleiteten State aus Inputs mit st.cache_data neu berechnen — nicht im Session-State speichern. Das hält den Session-State klein und macht die App korrekt, wenn sich Inputs ändern.
3. Warehouse-Integration: Queries vom Render-Cycle trennen
Streamlits Script-Rerun-Modell bedeutet, dass jede langsame Query im Render-Pfad den gesamten UI-Thread blockiert. Eine fünf-Sekunden-Warehouse-Query läuft bei jeder Interaktion, wenn sie nicht gecacht ist. Eine zehn-Sekunden-Query, die bei jedem Widget-Änderung neu ausgeführt wird, lässt die App auch für geduldige Nutzer kaputt wirken.
Das funktionierende Muster: Geplanten Refresh von interaktivem Render trennen. Ein Background-Job (ein Cron-Container, ein Airflow-Task oder ein dbt-Run) schreibt eine materialisierte View oder Parquet-Datei in einen Zwischenspeicher (S3, GCS oder ein Staging-Schema). Die Streamlit-App liest aus dem Zwischenspeicher mit kurzem TTL-Cache. Das Dashboard ist immer schnell; die Aktualität wird durch den Refresh-Schedule bestimmt, nicht durch die Geduld der Nutzer.
Für dbt-Nutzer: dbt-Modelle über ein Read-Only-Analytics-Schema exponieren und Streamlit daran anschließen — nicht an rohe Tabellen. Das liefert einen stabilen Schema-Vertrag zwischen Dashboard und Datenschicht, damit dbt-Refactors die App nicht still brechen.
4. Auth und Zugriffskontrolle
Streamlit Community Cloud bietet Google-basierte Auth. Self-Hosted-Streamlit hat keine eingebaute Auth. Die zwei Produktionsmuster:
Reverse-Proxy-Auth. Einen nginx- oder Caddy-Proxy mit OAuth2 Proxy oder Authelia vor den Streamlit-Prozess stellen. Der Proxy übernimmt den OIDC-Flow und leitet authentifizierte Requests weiter. Streamlit liest den Header X-Forwarded-User und verwendet ihn für rollenbasierte Logik. Das ist einfach, funktioniert mit jedem OIDC-Provider und hält Auth außerhalb des Anwendungscodes.
Anwendungs-level-Auth via st.experimental_user oder einem Drittanbieter-Paket. Bibliotheken wie streamlit-authenticator implementieren Login-Formulare in Streamlit. Das ist für schnelle interne Tools ohne bestehenden OIDC-Provider geeignet. Für alles, das an ein Unternehmens-SSO gebunden ist, ist der Reverse-Proxy-Ansatz sauberer und leichter auditierbar.
Rollenbasiertes Page-Routing: Streamlits Multipage-Feature routet nach Datei. Kombiniert mit einer Session-State-Variable, die vom Auth-Layer gesetzt wird (st.session_state.user_role), und einem frühen Exit-Check auf jeder Seite. Manuell, aber transparent.
5. Deployment: das Prozessmodell
Streamlit läuft als einzelner Python-Prozess mit mehreren Threads — einer pro aktiver Session. Das bedeutet:
- Horizontales Skalieren erfordert Sticky Sessions. Ein Load-Balancer, der denselben Nutzer an verschiedene Streamlit-Instanzen schickt, bricht den Session-State. Session-Affinity (cookie-basierte Sticky-Sessions in nginx oder Ihrem Cloud-Load-Balancer) verwenden, es sei denn, Sie verlagern Session-State nach Redis.
- Speicher ist zwischen Sessions nicht isoliert. Ein Memory-Leak im Code-Pfad einer Session betrifft alle Sessions. Speicher mit
tracemallocodermemory-profilerprofilen, bevor große Apps deployed werden. - Neustart ist nur mit Process-Manager zero-downtime. Gunicorn oder eine Container-Restart-Policy mit Health-Check-Endpoint verwenden. Streamlits eingebauter Server verarbeitet kein graceful Shutdown aktiver Sessions.
Für containerisierte Deployments: streamlit, pandas und Connector-Bibliotheken in requirements.txt mit exakten Versionen pinnen — keine Ranges. Ein pip install streamlit zwei Wochen später kann eine Version ziehen, die Widget-Verhalten ändert.
Streamlit-App in Produktion bringen
Wir reviewen Caching-Strategie, Session-Isolation, Auth und Deployment — bevor etwas bei Skalierung bricht.