streamlit / streamlit

Streamlit — A faster way to build and share data apps.
https://streamlit.io
Apache License 2.0
35.67k stars 3.09k forks source link

rerun causes streamlit server and web browser to keep consuming memory #4852

Closed astomnbkn closed 2 years ago

astomnbkn commented 2 years ago

Summary

I want my app keep rerun-ing periodically forever but streamlit server and web browser both keeps using more memory as they keep running repeatedly. I use a component to create timer on client side and let it do streamlit.setComponentValue(v) to kick rerun on server. streamlit 1.8.1 to 1.10.0 seems to have the same issue. Setting dataFrameSerialization "arrow" or "legacy", the same issue. I have no clue if this is related to using altair chart or not.

Am I missing something to implement automatic rerun from script so that it can rerun periodically?

Steps to reproduce

  1. generate a rather large dataframe
  2. visualize it using altair chart
  3. let the app keep running for some time (a few hours or over a night)
  4. see memory usage

Code snippet: This is sample code using altair chart from streamlit.io web site, modified to use functions to avoid global variables, plus a small code to do rerun. As a demo here streamlit-autorefresh component is used. I have my custom component which uses setTimeout() but the same issue occurs.

from time import sleep
import altair as alt
import pandas as pd
import streamlit as st
from vega_datasets import data

# We use @st.experimental_memo to keep the dataset in cache
@st.experimental_memo
def get_data():
    source = data.stocks()
    source = source[source.date.gt("2004-01-01")]
    return source

# Define the base time-series chart.
def get_chart(data):
    hover = alt.selection_single(
        fields=["date"],
        nearest=True,
        on="mouseover",
        empty="none",
    )

    lines = (
        alt.Chart(data, title="Evolution of stock prices")
        .mark_line()
        .encode(
            x="date",
            y="price",
            color="symbol",
        )
    )

    # Draw points on the line, and highlight based on selection
    points = lines.transform_filter(hover).mark_circle(size=65)

    # Draw a rule at the location of the selection
    tooltips = (
        alt.Chart(data)
        .mark_rule()
        .encode(
            x="yearmonthdate(date)",
            y="price",
            opacity=alt.condition(hover, alt.value(0.3), alt.value(0)),
            tooltip=[
                alt.Tooltip("date", title="Date"),
                alt.Tooltip("price", title="Price (USD)"),
            ],
        )
        .add_selection(hover)
    )
    return (lines + points + tooltips).interactive()

def get_annotation_layer():
    # Add annotations
    ANNOTATIONS = [
        ("Mar 01, 2008", "Pretty good day for GOOG"),
        ("Dec 01, 2007", "Something's going wrong for GOOG & AAPL"),
        ("Nov 01, 2008", "Market starts again thanks to..."),
        ("Dec 01, 2009", "Small crash for GOOG after..."),
    ]
    annotations_df = pd.DataFrame(ANNOTATIONS, columns=["date", "event"])
    annotations_df.date = pd.to_datetime(annotations_df.date)
    annotations_df["y"] = 10

    annotation_layer = (
        alt.Chart(annotations_df)
        .mark_text(size=20, text="⬇", dx=-8, dy=-10, align="left")
        .encode(
            x="date:T",
            y=alt.Y("y:Q"),
            tooltip=["event"],
        )
        .interactive()
    )
    return annotation_layer

def main():
    chart = get_chart(get_data())
    st.altair_chart(
        (chart + get_annotation_layer()).interactive(),
        use_container_width=True
    )

###############################################################################
#### rerun handling part
###############################################################################
if 'prev_count' not in st.session_state:
    st.session_state.prev_count = 0

from streamlit_autorefresh import st_autorefresh

def rerun_autorefresh(delay: float = 0):
    count = st_autorefresh(interval=delay*1000, limit=None, key="fizzbuzzcounter")
    st.session_state.prev_count = count

    # The function returns a counter for number of refreshes. This allows the
    # ability to make special requests at different intervals based on the count
    if count == 0:
        st.write("Count is zero")
    else:
        st.write(f"Count: {count}")

########
main()
st.write(f"Count: {st.session_state.prev_count}")
rerun_autorefresh(4)
python3 -m venv venv
. venv/bin/activate
pip install streamlit streamlit-autorefresh
streamlit run app.py

Expected behavior:

App keeps running periodically without keep consuming more memory.

Actual behavior:

App keeps consuming memory. And after sometime the performance of the app degrades and it takes longer time to plot the chart.

Is this a regression?

Probably. I used to use streamlit 0.7x and ReportThread hack on app python script to do rerunning. As I upgraded streamlit to recent versions, that hack is not usable. I switched to use timer on client side.

Debug info

Additional information

To compare, if I use experimental_rerun() within app by code like below,

from time import sleep
sleep(4)
st.experimental_rerun()

after a while app generates exception RecursionError like following. I do not know if this symptom relates to the issue of rerun.

2022-06-09 19:26:42.800 Uncaught app exception
Traceback (most recent call last):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/streamlit/scriptrunner/script_runner.py", line 554, in _run_script
    exec(code, module.__dict__)
  File "/home/user/work/streamlit-tests/streamlit_altair_chart_example_sleep.py", line 79, in <module>
    st.altair_chart(
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/streamlit/elements/dataframe_selector.py", line 337, in altair_chart
    return self.dg._arrow_altair_chart(altair_chart, use_container_width)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/streamlit/elements/arrow_altair.py", line 244, in _arrow_altair_chart
    marshall(
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/streamlit/elements/arrow_altair.py", line 361, in marshall
    chart_dict = altair_chart.to_dict()
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/altair/vegalite/v4/api.py", line 384, in to_dict
    dct = super(TopLevelMixin, copy).to_dict(*args, **kwargs)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/altair/utils/schemapi.py", line 338, in to_dict
    self.validate(result)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/altair/utils/schemapi.py", line 443, in validate
    return jsonschema.validate(
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 1056, in validate
    error = exceptions.best_match(validator.iter_errors(instance))
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/exceptions.py", line 356, in best_match
    best = next(errors, None)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 298, in ref
    yield from validator.descend(instance, resolved)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 332, in properties
    yield from validator.descend(
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_legacy_validators.py", line 113, in items_draft6_draft7_draft201909
    yield from validator.descend(item, items, path=index)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 368, in anyOf
    errs = list(validator.descend(instance, subschema, schema_path=index))
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 298, in ref
    yield from validator.descend(instance, resolved)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 332, in properties
    yield from validator.descend(
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 298, in ref
    yield from validator.descend(instance, resolved)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 332, in properties
    yield from validator.descend(
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 298, in ref
    yield from validator.descend(instance, resolved)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 298, in ref
    yield from validator.descend(instance, resolved)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 368, in anyOf
    errs = list(validator.descend(instance, subschema, schema_path=index))
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 298, in ref
    yield from validator.descend(instance, resolved)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 332, in properties
    yield from validator.descend(
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 368, in anyOf
    errs = list(validator.descend(instance, subschema, schema_path=index))
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 298, in ref
    yield from validator.descend(instance, resolved)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 368, in anyOf
    errs = list(validator.descend(instance, subschema, schema_path=index))
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 298, in ref
    yield from validator.descend(instance, resolved)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 368, in anyOf
    errs = list(validator.descend(instance, subschema, schema_path=index))
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 332, in properties
    yield from validator.descend(
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 298, in ref
    yield from validator.descend(instance, resolved)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 368, in anyOf
    errs = list(validator.descend(instance, subschema, schema_path=index))
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 298, in ref
    yield from validator.descend(instance, resolved)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 258, in descend
    for error in self.evolve(schema=schema).iter_errors(instance):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 242, in iter_errors
    for error in errors:
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 321, in type
    if not any(validator.is_type(instance, type) for type in types):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_validators.py", line 321, in <genexpr>
    if not any(validator.is_type(instance, type) for type in types):
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/validators.py", line 271, in is_type
    return self.TYPE_CHECKER.is_type(instance, type)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/jsonschema/_types.py", line 118, in is_type
    fn = self._type_checkers[type]
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/pyrsistent/_pmap.py", line 70, in __getitem__
    return PMap._getitem(self._buckets, key)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/pyrsistent/_pmap.py", line 61, in _getitem
    _, bucket = PMap._get_bucket(buckets, key)
  File "/home/user/work/streamlit-tests/venv/lib/python3.8/site-packages/pyrsistent/_pmap.py", line 55, in _get_bucket
    index = hash(key) % len(buckets)
RecursionError: maximum recursion depth exceeded while calling a Python object

Thank you.

randyzwitch commented 2 years ago

Hi @astomnbkn -

Try using st.experimental_singleton and see if that helps. Theoretically, that means a single copy of the data will only ever be present, whereas memo can place multiple copies in memory.

Best, Randy

astomnbkn commented 2 years ago

Hello @randyzwitch , Thanks for commenting.

I did not look closely enough and did not recognize st.experimental_memo was used in that code. I'm checking the cases of using no caching mechanism and using session_state. both is acting the same and after some hours streamlit server uses more memory. Probably the same for web browser.

The issue is memory consumption when rerun is done many times. My understanding is that when app is rerun, anything from previous run is not needed anymore and should be reclaimed at some later point in time, as long as it is not shared among reruns, like experimental_singleton or session_state.

I would like to do some more testing and get back here when I have something.

willhuang1997 commented 2 years ago

Hi @astomnbkn , closing this issue since it seems randy solved your problem. If you feel like there is a need to reopen it, please feel free to.