💸

streamlitをGCP Cloud Run上にデプロイすると課金され続ける問題について

に公開

はじめに

ここ最近LLMチャットアプリ自作のため、streamlitとlangchainを使って開発していました。ローカルで動かして遊んでいたんですが、やっぱりサーバ上で動かしたいよねってことで、サーバの選定を行うことに。
色々調べた結果、GCP Cloud Runはサーバレスであり、アクセスが発生したときのみ課金されるサービスとのこと。これなら運用時の費用もそこまでかからないだろうと思い、デプロイ(デプロイ自体の記事はネット上に多くあるので割愛)!
streamlitアプリをブラウザで開いた時やチャットしているときは課金され、アプリを触っていないときは課金されないという想定が、なんとブラウザでアプリを開いているだけで課金されている、、、!!!マジか、、、
無限に課金されるCloud Run
無限に課金されるCloud Run
流石にこれでは運用が厳しいということで調査してみた結果の報告であります。
なお、今回調査したstreamlitのバージョンは以下です。

streamlit                    1.44.1

調査の軌跡

シンプルなアプリをデプロイしてみる

自作したLLMチャットアプリはいつの間にか複雑化していたので、自分が実装した処理がまずいのか、それともstreamlit自体に問題があるのか切り分けが難しい状態。そこでまず、LLMチャットなどは無しにして超シンプルなstreamlitアプリをデプロイ。
シンプルstreamlitアプリ
シンプルstreamlitアプリ
コードはこれだけ。

import streamlit as st

st.set_page_config(page_title="Chat App")
st.title("Chat App")

st.markdown("test1")
st.markdown("test2")

このアプリをブラウザで開いた状態で課金状態をチェックしてみたところ、無限課金状態になっていたので、LLMチャットアプリ自体は問題無いことが判明(よかった、、、)。
この環境で色々観測していたところ、ブラウザのアプリを開いているときは課金され、アプリのタブを閉じると課金されなくなるという挙動に気づく。
課金状態
課金状態(ブラウザのタブを閉じるとグラフが下がる)

streamlitのログを見てみる

次にログからなんか分かるかな?ということで、streamlit起動コマンドにてログレベルをdebugにしてデプロイ、Cloud Runコンソールからログを見てみました。

uv run streamlit run minimum_streamlit.py --server.port $PORT --logger.level DEBUG

すると、5分おきに以下のようなログがでていることに気づく。

"2025-05-01 03:55:54.671 Runtime state: RuntimeState.ONE_OR_MORE_SESSIONS_CONNECTED -> RuntimeState.NO_SESSIONS_CONNECTED"
...
"2025-05-01 03:55:55.084 Runtime state: RuntimeState.NO_SESSIONS_CONNECTED -> RuntimeState.ONE_OR_MORE_SESSIONS_CONNECTED"

セッションが無くなったから張りなおしたよ!みたいな感じだろうか。そしてなぜ、5分おきにこれが実行されるんだろう。

streamlitのコードを見てみる

ログだけ見ててもこれ以上は進まず、今度はstreamlitのコードを確認。セッション管理をやってるのは以下だと思われる。
https://github.com/streamlit/streamlit/blob/e3be963a0e3293a1a79bfff82195d98dad064dd7/lib/streamlit/runtime/runtime.py#L642-L660
詳細は割愛するが、深堀していくと内部でTornadoというネットワーキングライブラリを使っているようで、定期的にCloud Runサーバに対してpingを打っている。そしてpingが通らなくなると例外を上げてくるので、その例外をstreamlitがキャッチしてセッションを張り直しているようだ。
つまり、整理すると以下のようなことが起きていると想定される。

  1. streamlitのアプリを起動
  2. 内部でTornadoがCloud Runのサーバに対してpingを定期的に打つ
  3. アプリの操作が無いとCloud Runのサーバが停止する
  4. pingが通らなくなるため例外が発生、streamlitがそれを検知してとCloud Runのサーバに何かしらの働きかけを行い、セッションを復活させる
  5. 永遠にCloud Runサーバが起動し続け、課金される

試しにCloud Runにデプロイしたアプリの設定コンソールより、以下の設定を300 -> 60に変えてみたところ
リクエストタイムアウト設定
リクエストタイムアウト設定
以下のログの発生周期が60秒に変わったため、これはアプリが何秒触られていないとサーバが落ちるという設定であり、上の挙動とも辻褄が合う。

Runtime state: RuntimeState.ONE_OR_MORE_SESSIONS_CONNECTED -> RuntimeState.NO_SESSIONS_CONNECTED"

まとめ

その後、Tornadoからpingを打たないようにしたり、streamlitのセッション貼り直しを無効化する設定が無いか探してみたが見つけられず(これを実現するにはstreamlitの改造が必要そう)、、、
また、この件は本家の開発掲示板でも度々話題になっている。
https://discuss.streamlit.io/t/streamlit-app-deployment-expensive-on-cloud-run/65337/3
流石にブラウザでアプリ開いてるだけでずっと課金されてしまうと費用的に厳しいし、Cloud Run使ってる意味あるんか?とも思ってしまうので、これは改善されると嬉しいなぁ。

もし対策を知っている方がいらっしゃれば教えて頂けると大変助かります!

とりあえず、現状できる対策としては、使わない時はアプリのタブを閉じろ! だ。

Discussion