Websocket message size limits with large st.dataframe payloads
0 reputation · 26 Apr 2025, 09:16 UTC
0 reputation · 26 Apr 2025, 09:16 UTC
Streamlit uses client-side virtualization for st.dataframe, which optimizes how rows are rendered in the browser. However, the full dataset must still be serialized and transmitted from the server to the client via websockets before virtualization occurs.
When handling large datasets, this architecture creates a bottleneck where the server memory may be sufficient, but the network payload exceeds practical websocket limits or triggers browser-side memory exhaustion. To mitigate this, developers often implement query bounding or manual pagination using st.session_state to slice the data before it reaches the rendering component.
There is a design trade-off between loading a single large bounded chunk into st.cache_data for fast client-side scrolling and implementing server-side pagination that re-queries the source on every page change.
st.data_editor modifications and cached query results affect the consistency of paginated views across script reruns?32275 reputation · 26 Apr 2025, 20:18 UTC
st.dataframe and st.data_editor utilize client-side virtualization, which means only the visible rows are rendered in the DOM. However, the **entire dataset** must be serialized (typically into Apache Arrow or JSON) and sent over the WebSocket before this virtualization can take place.
When dealing with large payloads, the failure is usually not a hard 'limit' within Streamlit itself, but rather one of three factors:
client_max_body_size or timeout settings that drop large WebSocket frames.To handle datasets that exceed practical payload sizes, move away from client-side virtualization toward server-side control:
Instead of passing the full DataFrame to st.dataframe, use st.session_state to track the current page and slice the data on the server.
import streamlit as st
import pandas as pd
if 'page' not in st.session_state:
st.session_state.page = 0
page_size = 100
# Load data once cached
df = st.cache_data(load_large_data)
# Slice data before sending to client
start = st.session_state.page * page_size
end = start + page_size
st.dataframe(df.iloc[start:end])
col1, col2 = st.columns(2)
with col1:
if st.button("Previous") and st.session_state.page > 0:
st.session_state.page -= 1
st.rerun()
with col2:
if st.button("Next") and end < len(df):
st.session_state.page += 1
st.rerun()
If the user needs to see specific subsets, implement filters (date ranges, category selectors) that filter the DataFrame before it reaches the rendering component. This ensures the WebSocket payload remains small regardless of total dataset size.
The optimal threshold for switching to manual paging is hardware-dependent; generally, datasets exceeding 50,000–100,000 rows (depending on column count) will cause noticeable lag in standard Streamlit components. Regarding consistency with st.data_editor: if you modify a paginated view, you must manually map those changes back to the master cached DataFrame in st.session_state, as the component only knows about the current slice.
To provide a more specific recommendation, are you experiencing specific connection-drop errors, or is the browser UI simply freezing?
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.