Streamlit in Snowflakeでアプリを作る方法

REVISIOのエンジニア片岡です。

Pythonのみで簡単にダッシュボード的なWebアプリが作れるフレームワークStreamlitですが、昨年Snowflakeに買収され、 Snowflakeのウェアハウス(サーバー)上で実行してユーザーへ提供できるようになりました。

  1. Snowflake Native AppのUIとしてStreamlitを使用
  2. Native AppではなくシンプルにSnowflake上でStreamlitを実行

の2種類がありますが、今回は2について、どのような開発・利用体験となるのか確認してみました。

おおまかな流れは以下のようになります。

  1. Streamlitアプリをローカルで作成して動作確認
  2. Snowflakeの内部ステージにコードをアップロード
  3. 上記のコードを使用してSnowflake上にStreamlitアプリを作成
  4. ブラウザで実行

1. Streamlitアプリをローカルで作成して動作確認

ファイル構成は以下のようになります。Dockerで環境構築します。

.
├── app
│   ├── .streamlit
│   │   └── secrets.toml  ※Snowflake接続情報を記載
│   ├── environment.yml   ※Snowflake環境にインストールしたいPythonパッケージ定義
│   └── streamlit_app.py  ※アプリケーションコード
└── docker-compose.yml
└── Dockerfile

Dockerfileはこちら。pipでstreamlit、pandas、snowflake-connector-python、snowflake-snowpark-python、plotly等をインストールします。

FROM python:3.8-slim

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    vim \
    && rm -rf /var/lib/apt/lists/*

RUN pip install --upgrade pip
RUN pip install streamlit pandas snowflake-connector-python snowflake-snowpark-python plotly

CMD ["streamlit", "run", "--server.port", "8501", "streamlit_app.py"]

そしてdocker-compose.ymlはこちらです。※現在のDockerではデフォルトのファイル名はcompose.yamlらしいのですが、まだ慣れないですね・・

version: '3'
services:
  streamlit:
    build:
      context: .
    ports:
      - 8501:8501
    volumes:
      - ./app:/app

そして下記がStreamlitを利用したアプリのコード(streamlit_app.py)です。

ローカル環境用ではsecrets.tomlからSnowflake接続情報を取得し、Snowflake環境上ではアクティブセッション(現在Snowflakeにログインしているセッション)を使用してSnowflakeのデータにアクセスします。

import os
import pandas as pd
import plotly.express as px
import streamlit as st
import snowflake.connector
from snowflake.snowpark.context import get_active_session

# SnowflakeでSQLを実行
database: str = 'DEV'
query = f'select ..... from {database}.table_name ....' # {database}については後述

# ローカル環境では.streamlit/secrets.tomlからSnowflake接続情報取得、
# Snowflake環境ではアクティブセッション(Snowflakeにログインしているユーザー)を使用
if os.path.isfile('.streamlit/secrets.toml'):
    secrets = st.secrets["snowflake"]
    conn = snowflake.connector.connect(
        user=secrets["user"],
        password=secrets["password"],
        account=secrets["account"],
        warehouse=secrets["warehouse"],
        database=secrets["database"],
        schema=secrets["schema"]
    )
    df = pd.read_sql_query(query, conn)
else:
    session = get_active_session()
    # USEの実行がエラーになり、データベースの指定ができないため↑のSQLでデータベースを記載する形としている
    # session.use_database('xxx') 
    # session.use_warehouse('xxx')
    df = session.sql(query).to_pandas()

# 取得データをテーブル表示
st.write(f'## テレビCM出稿量ランキング {target_date}')
st.write(df)

# バーチャート表示
df = df.reset_index(drop=True).set_index('NAME')
df.sort_values(by=df.columns[0], ascending=True, inplace=True)
fig = px.bar(df, orientation='h')
st.plotly_chart(fig)

Snowflakeへの接続情報は.streamlit/secrets.tomlに記載していますが、Snowflakeにこのファイルをアップするのはセキュリティ的によくなさそうなので、Snowflake上で実行する場合はSnowflakeにログインしているユーザー(Streamlitアプリ利用時にログインさせる仕様になっている)のセッションを使用する形にしてみました。

この辺りのベストプラクティスを知りたい所です。

.streamlit/secrets.tomlの書式は以下の通りです。

[snowflake]
user = "xxx"
password = "xxx"
account = "xxx" # ex) xxxxx.ap-northeast-1.aws
warehouse = "xxx"
database = "xxx"
schema = "xxx"

Dockerをビルド&起動します。

$ docker compose up -d

ブラウザで http://localhost:8501を開くと、以下のように表示されました。

非常に少ないコードでWebアプリができましたね。

ちなみにこれは当社で取得している、GRP(CMが放送されたテレビ番組の世帯視聴率の積み上げ)というデータになります。

Streamlitアプリ

2. Snowflakeの内部ステージにコードをアップロード

ローカル環境で動作確認ができたので、これをSnowflake上で稼働させる作業をしていきます。

まずはSnowflakeの内部ステージに、今回作成したコードをアップするわけですが、Dockerfileとdocker-compose.ymlはアップしません。

ではDockerfileでやっていた、必要なパッケージのインストールはどうするかと言うと、environment.ymlに記載して、environment.ymlをアップしておくと、Snowflake上でのアプリ起動時にインストールしてくれます。

dependencies:
  - plotly
  - pandas

※streamlit、snowflake-connector-python、snowflake-snowpark-python等はデフォルトでインストールされているので別途インストールする必要はありません。

※当初、パッケージのインストール方法が分からず、ModuleNotFoundError: No module named 'plotly' というようなエラーが出ていましたが、ちゃんとドキュメントにも記載がありました。

次に、Snowflakeデータベースに対してファイルをアップロードする際の保存先となる内部ステージを作成します。

Snowflakeデータベースに対して下記SQLを実行します。

-- devデータベースのworkスキーマにstreamlit_sample_kataokaという内部ステージを作成
CREATE STAGE dev.work.streamlit_sample_kataoka DIRECTORY = ( ENABLE = true );

そして、内部ステージにファイルをアップロード(PUT)します。

PUT 'file:///Users/m.kataoka/Documents/streamlit-sample/app/*'
@dev.work.streamlit_sample_kataoka 
AUTO_COMPRESS = FALSE OVERWRITE = TRUE;

3. Snowflake上にStreamlitアプリを作成

上記2でアップしたファイルを使用してSnowflake上にStreamlitアプリを作成します。

Snowflakeデータベースに対して下記SQLを実行します。

CREATE OR REPLACE STREAMLIT streamlit_sample_kataoka  -- Streamlitアプリ名
ROOT_LOCATION = '@dev.work.streamlit_sample_kataoka'  -- 先ほど作成した内部ステージ
MAIN_FILE = 'streamlit_app.py'
QUERY_WAREHOUSE = WH_DEV_TEMP; -- Streamlitアプリを実行するウェアハウス

4. ブラウザで実行

SnowflakeのWeb UI(Snowsight)にログインし、左メニューの「Streamlit」を開くと、作成したアプリが表示されます。

SnowsightでのStreamlit

そしてアプリ名をクリックすると、以下のようにローカル環境と同じものが表示されました。

Streamlitアプリサンプル

エンドユーザー向けに共有するには、右上の「共有」ボタンを押して、URLを取得します。

見ての通り、https://app.snowflake.com/.... というように、Snowflake上でホストされていることが分かります。

共有

このURLをユーザーが開くと、まずはログイン画面が表示されます。

事前にSnowflake上にこのアプリの使用権限のあるロールを持ったユーザーを作成しておき、ユーザーへ通知しておく必要があります。

ログイン後にアプリを利用することができます。

ログイン画面

まとめ

かなりシンプルにStreamlitアプリをSnowflake上でホストすることができました。

AWS等のサーバーやコンテナサービス上でホストするよりも簡単にできるのが大きな違いだと思います。

Snowflakeのウェアハウスは時間単価としては高いですが、利用する時だけの課金なので、場合によってはAWSのECS等で常時稼働するより安価になるかもしれません。

また、Snowflake上でのStreamlitについて(Native Appも含め)、使用できない機能等の制約がたくさんあります

特に、unsafe_allow_html=True が使用できないのはデザインの調整時に困ることになりそうです。

カスタムコンポーネントが使用できないのも、本格的にアプリ開発していく際には障害になるかもしれません。

現在プライベートプレビュー中のため、正式公開時には色々と変更されている可能性があります。

期待して待ちたいと思います!