
データアプリフレームワークとしてのStreamlit / Dash
StreamlitとDashは、Python開発者が簡単にウェブベースのデータアプリを開発できる強力なフレームワークです。
両者はデータサイエンティストやアナリストがダッシュボードやレポートを簡単に作成してチームに共有できるという点で価値を発揮しています。しかし、これらは明確に得意なユースケースが異なります。
- Streamlit: 小規模チームでの素早いデータ活用
- Dash: エンタープライズレベルでスケールする大規模運用
この記事では、これらのユースケースの違いの背景にある、それぞれの強みや機能上の違いを解説します。
採用しているアーキテクチャが最大の違い
StreamlitとDashでは、採用しているアーキテクチャに大きな違いがあります。このアーキテクチャの違いは、それぞれのフレームワークが目指している世界観に寄り添うものであり、フレームワークの強みにも鮮明に現れています。
Streamlit | Dash | |
---|---|---|
Webサーバー / フレームワーク | Tornado | Flask |
強み | ユーザーの入力を即座にUIに反映 | サーバーのスケールアウトが可能 |
弱み | エンタープライズレベルのスケーラビリティと認証・認可 | Pure PythonよりもWEBフロントエンドの知識が必要 |
Streamlit: データサイエンティストがWebアプリ構築をするためのフレームワーク
Streamlitは、データサイエンティストが手元のPythonスクリプトからインタラクティブなWebアプリに変換することを簡単にするために生まれました。データサイエンティストの構築した分析モデルをフロントエンドから試すためには、スライダー移動やフィルタ選択などのユーザー操作が発生するたびにスクリプトを実行して結果をリアルタイムに見れる必要があります。
ユーザーのイベントを高速かつ軽量に捌くために、StreamlitはWebサーバーにTornadoを採用してWebsocketを活用した非同期通信とReactの仮想DOMの仕組みを組み合わせて構築されています。
Tornadoの1つのプロセスで大量のコネクションを捌くことができる強みを最大限に生かして、ユーザーの入力に対して高速なUI更新を実現しています。
この仕組みのおかげで以下のようにHTML, JavaScriptのコールバック処理を書かなくてもユーザーの操作を即座に画面に反映をすることができます。
import streamlit as st
value = st.slider('Please select the value.', 0, 100, 50)
st.write(f"Selected value is {value}")
Dash: BIダッシュボードをスケーラブルに構築するためのフレームワーク
Dashは、可視化ライブラリPlotlyを開発しているチームが開発したフレームワークです。Pythonでダッシュボードを構築を強力に行える強みを持っています。
ダッシュボードはその特性上チーム全体で使用されるため、エンタープライズにおいてはスケーリングが非常に重要です。Dashは、Webアプリケーション開発でもよく使われるFlaskをバックエンドに採用しています。
ユーザーの入力に対するコールバックはリクエスト単位で行われるため、セッションの状態をサーバープロセスに溜め込まないため、ステートレスな通信でUIの更新を実現しています。
Dashは、この仕組みのおかげでGunicornなどを利用してWSGIアプリとして複数プロセスを立ち上げて稼働させることができます。そのため、エンタープライズレベルの規模でアプリを使用しても、単純な設定でサーバーの台数を増やし水平スケールを行うことができます。
実装では、アプリのレイアウトとコールバック関数を定義して、Input/Outputをデコレータで定義をする。
from dash import Dash, html, dcc, callback, Output, Input
import plotly.express as px
import pandas as pd
df = pd.read_csv('./data.csv')
app = Dash()
app.layout = [
html.H1(children='Title of Dash App', style={'textAlign':'center'}),
dcc.Dropdown(df.country.unique(), 'Canada', id='dropdown-selection'),
dcc.Graph(id='graph-content')
]
@callback(
Output('graph-content', 'figure'),
Input('dropdown-selection', 'value')
)
def update_graph(value):
dff = df[df.country==value]
return px.line(dff, x='year', y='pop')
if __name__ == '__main__':
app.run(debug=True)
シンプルでピュアなPythonだけで完結するのはStreamlit
StreamlitのリアルタイムにUIをWebsocketで更新をするアーキテクチャは、コールバック処理を開発者が意識することから解放してくれます。
一方で、DashではAPIリクエストでステートレスにコールバックを扱う必要があるためHTMLを意識したコンポーネントの記述とコンポーネント間の依存関係を明示的に宣言する必要があります。
以下のように、Pythonだけでは書けるもののHTMLやJavaScriptの考え方を理解している必要があります。
app.layout = html.Div([
html.H1("My Dash App"),
dcc.Input(id='input-box'),
html.Div(id='output-div')
])
@app.callback(
Output('output-div', 'children'),
Input('input-box', 'value')
)
def update_output(value):
return f'Value: {value}'
これに対してStreamlitでは、同じことを極めてシンプルに実現ができます。Pythonのスクリプトを上から書けばブラウザで自動で反映されるのです。
st.title("My Streamlit App")
value = st.text_input("Please input text")
st.write(f"Value: {value}")
また、Streamlitでは、各ユーザーごとにPythonスレッドを占有する形になるため st.session_state
のようにユーザーの状態を簡単に共有することができます。
このように共にPythonのみでデータアプリが構築できるフレームワークですが、Streamlitの方がよりピュアなPythonのみの知識でアプリを直感的に構築することができます。
エンタープライズ要件ではDash
スケーラビリティの課題
Streamlitのアーキテクチャは実装のシンプルさや高速なUI更新を実現する一方で、各接続ごとにPythonスレッドとUIのオブジェクトを保持する必要があります。この構成は、同時接続数が多くなるとRAMが直線増することに繋がります。更にサーバーをスケールした場合にも、ロードバランサー層で同じクライアントのリクエストを必ず同じサーバーに転送する設定を行う必要があるため、スケールアウト時にインフラの構成が複雑化します。
一方でDashは、既にWebアプリケーションで実績のあるWSGIをベースにした構成をとっているため簡単にスケールアウトができるためチームのサイズに合わせたインフラ構成を簡単に実現することができます。
この点ではDashの方がスケールに合わせた心配事が少ないと言えるでしょう。
認証やRBAC(Role-based access control)との統合
エンタープライズではセキュリティにまつわる要件として必ず認証やRBACの対応が必要になります。Dashでは、Dash Enterpriseの機能を使うことでエンタープライズレベルの認証やRBACへの対応が可能なだけでなくフレームワークとしてもFlaskの強力なWeb開発のエコシステムを利用することができます。 Flask-OAuthlib
Flask-Login
Flask-Security
などの認証フレームワークをそのまま統合することで実装もシンプルになります。
@login_required
のように手軽に保護されたルートを作れるため、コードを複雑にせずに済みます。
from dash import Dash, html
from flask import Flask, redirect
from flask_login import LoginManager, login_user, login_required, UserMixin
server = Flask(__name__)
server.secret_key = 'your-secret-key'
login_manager = LoginManager()
login_manager.init_app(server)
class User(UserMixin):
def __init__(self, id):
self.id = id
@login_manager.user_loader
def load_user(user_id):
return User(user_id)
app = Dash(__name__, server=server)
@server.route('/login')
def login():
login_user(User('user123'))
return redirect('/')
app.layout = html.Div([
html.H1('Dash App'),
html.Div(id='content'),
])
@app.callback(
output={'id': 'content', 'property': 'children'},
inputs=[]
)
@login_required
def protected_content():
return 'You are logged in!'
if __name__ == '__main__':
app.run_server(debug=True)
一方、Streamlitは、Tornadoベースの非同期サーバー + Websocket中心の通信という構成から既存の認証エコシステムの活用が難しく、WebSocketのセッション管理と組み合わせて実装をする必要があります。
公式で st.login
や st.experimental_user
などのコンポーネントが用意されていますが、認証状態の管理を st.session_state
で独自に行う必要があり、RBACなどの複雑なロール管理はさらに複雑になります。
このようにその独自の通信方式から実装ベースのベストプラクティスを持ち込みにくいことや、自前実装が増えることによるリスクの増加が課題になります。
import streamlit as st
if not st.experimental_user.is_logged_in:
if st.button("Log in with Google"):
st.login()
st.stop()
if st.button("Log out"):
st.logout()
st.markdown(f"Welcome! {st.experimental_user.name}")
結論
いかがでしたでしょうか。StreamlitもDashもPythonのみでデータアプリを構築することができる非常に強力なツールですが、ユースケースに合わせて全く相対する強みがあります。
フェーズに合わせて少数チームの時はStreamlitから始めて、チームのスケールに伴ってDashに移行する運用も一般的です。
最後に、弊社で開発しているSquadbaseはStreamlitをスケーラブルにホスティングする技術を開発しています。更にSquadbase上にデプロイしたStreamlitアプリには認証やアクセスコントロールなどのエンタープライズ機能がビルトインで搭載されます。
Squadbase小さくStreamlitで始めた運用をコードベースを置き換えずにそのままエンタープライズ運用ができる選択肢になります。