After Upload file stays in RAM

Please take a moment to search the forum and documentation before posting a new topic.
If you’re creating a debugging post, please include the following info:

  1. Are you running your app locally or is it deployed? local

Streamlit Version 1.49.1
Python Version 3.13.7

I searched for the issue on all known databases without success :frowning:

I am facing the following issue with st.file_uploader:

When I am uploading a File about 600MB the RAM usage is after I selected the File about 660 MB.
At the moment I pressed the upload button the RAM usage increases up to 1800MB.
After the Upload is done, the RAM usage stays at 660MB.
If I close the browsertab it stays at 660MB, only when I close the streamlit instance the RAM gets free.

I tryed several solutions:

  • clear_on_submit=True
  • working with session state keys
  • gc.collect()
  • st.rerun()

The phenomenon is:
If I set the clear_on_submit=false and deactivate the st.rerun() I am able to delete the File in the Widget and only then the file gets deleted from the RAM.

I tested it with a simple Upload code with the same result.

Actually I have no idea how to fix this, can somebody help?

here my Upload Code:
-------- Upload-Helper --------

def save_uploaded_file(file, folder):
os.makedirs(folder, exist_ok=True)
save_path = os.path.join(folder, file.name)

with open(save_path, "wb") as f:
    shutil.copyfileobj(file, f)

return save_path

def unified_upload(device_id):
st.subheader(“:open_file_folder: Dokument hochladen”)

# Dokumenttypen + erlaubte Dateiendungen
doc_types = {
    "Firmware": {"label": "💾 Firmware", "allowed_types": ["zip", "pdf"]},
    "Manual": {"label": "📘 Servicemanual", "allowed_types": ["pdf"]},
    "Partslist": {"label": "🛠️ Partslist", "allowed_types": ["pdf"]},
    "Bulletin": {"label": "📑 Bulletin", "allowed_types": ["pdf"]},
}

# Upload-Counter initialisieren
if "upload_counter" not in st.session_state:
    st.session_state.upload_counter = {}

if device_id not in st.session_state.upload_counter:
    st.session_state.upload_counter[device_id] = 0

# Dropdown zur Auswahl des Dokumententyps
doc_choice = st.selectbox(
    "Dokumenttyp auswählen",
    options=list(doc_types.keys()),
    format_func=lambda x: doc_types[x]["label"],
    index=None,
    placeholder="Bitte auswählen...",
    key=f"doc_select_{device_id}"
)

if not doc_choice:
    st.info("Bitte zuerst den Dokumenttyp auswählen ⬆️")
    return

# Zielordner für Gerät & Typ
folder = os.path.join(r"D:\ServiceInfoToolData", str(device_id), doc_choice)
os.makedirs(folder, exist_ok=True)

# Dynamischen Key für file_uploader
file_key = f"file_upload_{device_id}_{doc_choice}_{st.session_state.upload_counter[device_id]}"

# Upload-Formular mit Auto-Reset
with st.form(f"upload_form_{device_id}_{doc_choice}", clear_on_submit=True):
    file = st.file_uploader(
        f"{doc_types[doc_choice]['label']} hochladen",
        type=doc_types[doc_choice]["allowed_types"],
        key=file_key
    )

    title = st.text_input("Titel (optional)")
    submitted = st.form_submit_button("Speichern")

    if submitted:
        if file is None:
            st.error("Bitte eine Datei auswählen.")
        else:
            save_path = save_uploaded_file(file, folder)

            # DB-Eintrag speichern
            conn = get_connection()
            try:
                conn.execute(
                    """
                    INSERT INTO serviceinfos 
                    (geraet_id, typ, dateiname, pfad, uploaded_at, version) 
                    VALUES (?,?,?,?,?,?)
                    """,
                    (
                        device_id,
                        doc_choice,
                        file.name,
                        save_path,
                        datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                        title.strip() or None,
                    ),
                )
                conn.commit()
            finally:
                conn.close()

            # Upload-Counter hochzählen, damit nächster Upload einen neuen Key bekommt
            st.session_state.upload_counter[device_id] += 1

            # Speicher freigeben
            del file
            st.session_state._uploaded_file_mgr = UploadedFileManager()
            gc.collect()
            st.rerun()

            st.success(f"{doc_types[doc_choice]['label']} hochgeladen ✅")

This cant be real… only by uploading an 800MB File with Streamlit 4 Times, nobody here with an idea how to solve this?
Actually the RAM gets free when I close the Browsertab.
Thats a little hope.

When you use st.file_uploader, the file does indeed stay in memory. If you want the file to be saved on disk, you’ll have to do that yourself. And yes, this information in memory will clear when the session ends (when the tab is closed).

Are you saying the the uploaded files are staying in memory after they are cleared from the widget, like a memory leak?

Yes it stays in RAM after the Upload is done and the Session is cleared.

  1. file choose
  2. Upload
  3. Session clear via changing Session keys, clear_on_submit=true etc.

Result: File stays in RAM.

The only way to delete the file from RAM is to set clear_on_submit=false and delete the file manually from the Widget by pressing the x for removing the file.

I tried this on 2 PCs with several browsers.

I have no solution for this issue.

Please can you open a GitHub issue for our devs to investigate this.

Done: After File Upload the file stays in RAM · Issue #12607 · streamlit/streamlit · GitHub

Same issue with Streamlit 1.50

When building internal tools, especially for heavy tasks like firmware development where users frequently upload massive .zip archives or binary packages, this memory leak behavior with st.file_uploader becomes a massive bottleneck. The core issue is that Streamlit’s backend architecture binds the uploaded file object tightly to the active browser session payload.

Even when you use clear_on_submit=True inside an st.form, or try to force garbage collection with gc.collect(), the internal Tornado web server that Streamlit runs on holds onto the memory reference until the websocket connection is physically broken, like closing the browser tab.

Until the GitHub issue you opened gets patched by the developers, here is the most reliable workaround the community uses to keep the RAM footprint low when dealing with large files:

Instead of relying on the built in clear_on_submit, you can manually remove the file from the widget’s session state immediately after you save it to the disk.

You can try adding this logic right after your database commit:
Save file to disk first

save_path = save_uploaded_file(file, folder)

Then explicitly delete the widget key from session state

if file_key in st.session_state:
del st.session_state[file_key]

By forcibly removing the key from st.session_state before calling st.rerun(), you break the reference holding the file in memory, mimicking the behavior of the user clicking the “X” on the widget. It is a bit of a hack, but it is currently the best way to keep your server from crashing under the weight of large uploads!