skip to content
barorin&

Streamlitアプリの公開と運用まとめ|ポート設定・キャッシュ・スリープ対策

/ 7 min read

Table of Contents

はじめに

Streamlitは、Pythonのスクリプトをそのままブラウザで動くアプリにできるライブラリです。手元で動かすのは簡単ですが、公開して運用しようとすると、ポートやキャッシュでつまずきます。

実際に引っかかったところをまとめました。

起動とポートの指定

既定では8501番で起動します。

Terminal window
$ streamlit run app.py

ポートを変えたい場合は引数で指定します。

Terminal window
$ streamlit run app.py --server.port 8080

外部からアクセスさせる場合は、待ち受けるアドレスも指定します。

Terminal window
$ streamlit run app.py --server.address 0.0.0.0 --server.port 8080

Cloud9で動かす

AWS Cloud9で開発している場合は、8080番で起動してからプレビュー機能を使います。

Terminal window
$ streamlit run app.py --server.port 8080

画面上部のツールバーから Preview > Preview Running Application を選ぶと、IDE内のブラウザで確認できます。

80番ポートで公開する

ブラウザからポート番号なしでアクセスさせるには、80番で待ち受ける必要があります。ただし1024番未満のポートは、root権限がないと直接バインドできません。

iptables で転送するのが簡単です。

Terminal window
$ sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8501

80番宛の通信を8501番に転送するので、Streamlit自体は通常どおり8501番で起動したままにできます。

キャッシュを使い分ける

Streamlitは、操作のたびにスクリプト全体を上から再実行します。そのため、重い処理はキャッシュしないと毎回走ります。

キャッシュのコマンドは、バージョン1.18.1から次の2つに整理されました。以前の st.cachest.experimental_memost.experimental_singleton は置き換えられています。

デコレータ 用途
@st.cache_data データを返す処理 DataFrameの読み込み、API取得、計算結果
@st.cache_resource 接続やオブジェクト DB接続、機械学習モデルのロード

判断の基準は、戻り値をコピーして返してよいかどうかです。データはコピーして問題ありませんが、DB接続をコピーすると別物になってしまうため、cache_resource で同じオブジェクトを共有します。

import pandas as pd
import streamlit as st
from sqlalchemy import create_engine
@st.cache_resource
def get_db_engine():
"""DB接続(アプリ全体で1つを共有する)"""
url = "sqlite:///hoge.db"
return create_engine(url, connect_args={"check_same_thread": False})
@st.cache_data
def get_df():
"""取得したデータをキャッシュする"""
engine = get_db_engine()
return pd.read_sql(sql="SELECT * FROM fugas", con=engine)

create_enginesqlalchemy からのインポートが必要です。

Community Cloudのスリープ対策

Streamlit Community Cloudは無料でアプリをホストできますが、**一定期間アクセスがないとスリープします。**次に開いた人は起動待ちの画面を見ることになります。

定期的にアクセスして起こしておく方法があります。

Terminal window
$ pip install selenium chromedriver-binary-auto
from time import sleep
import chromedriver_binary # noqa: F401 # インポートするだけでパスが通る
from selenium import webdriver
def web_open(url: str) -> None:
options = webdriver.ChromeOptions()
options.add_argument("--headless")
driver = webdriver.Chrome(options=options)
try:
driver.implicitly_wait(120)
driver.get(url)
sleep(30) # 起動を待つ
finally:
driver.quit() # 必ず閉じる

これをcronで定期実行します。cronからの実行方法はPythonのvenvとpipまとめに書いています。

Terminal window
# 6時間おきにアクセスする
0 */6 * * * cd /home/ubuntu/app; /home/ubuntu/app/venv/bin/python wake.py >> /tmp/wake.log 2>&1

おわりに

Streamlitは書きやすい反面、「スクリプト全体が毎回再実行される」という前提を知らないと、なぜ遅いのかが分かりません。重い処理を見つけたら、まず cache_datacache_resource のどちらが適切かを考えてみてください。

公開まわりは、手元で試すなら --server.port、外部に出すならnginxを前段に置く、と切り分けておくと迷いません。

この記事を書いた人

barorinのプロフィール画像

barorin公認会計士

会計とITの二足のわらじで働く公認会計士です。Pythonを中心に、会計・監査の実務で実際に使っているコードや、Ubuntu・Raspberry Piの設定メモを書いています。