Squadbase

Streamlitアプリのパフォーマンス改善と運用戦略

Streamlitアプリケーションのパフォーマンス最適化と本格運用のための戦略を学習する

これまでの章では、Streamlitを使ったBIダッシュボードの基本的な構築方法を学んできました。しかし、実際のビジネス環境では、開発したアプリケーションを継続的かつ安定的に運用するための仕組みが不可欠です。

本章では、Streamlitアプリケーションを本格的な運用に向けて発展させる方法について解説します。単なる開発で終わらせるのではなく、チーム全体で継続的に価値を提供し続けるためのプロセスを構築していきます。

なぜStreamlitにDevOps・DataOpsが必要なのか

従来のBIツール運用との違い

従来のBIツールとStreamlitでは、運用のアプローチが大きく異なります:

項目従来のBIツールStreamlitアプリ
開発者ITチーム専属ビジネスユーザー + エンジニア
変更管理チケット駆動コードレビュー駆動
デプロイ管理者権限必要GitHubプッシュで自動化
スケーリングライセンス購入インフラ設定調整
カスタマイズ性制限ありほぼ無制限
運用コスト月額固定費使用量ベース

この変化により、新たな課題が生まれます:

  • コードの管理: 複数人でのコード変更をどう管理するか
  • 環境の一貫性: 開発環境と本番環境の差をどう無くすか
  • アクセス制御: 誰がどのデータにアクセスできるかの管理
  • パフォーマンス: 大量のユーザーや大容量データに対応できるか
  • 運用監視: アプリケーションの稼働状況をどう把握するか

これらの課題に対応するため、DevOps・DataOps(継続的インテグレーション・継続的デプロイメント)の考え方と手法をStreamlitプロジェクトに適用する必要があります。

Squadbaseを活用した効率的なデプロイメント

従来のデプロイ方法との比較

Streamlitアプリケーションのデプロイには、これまでいくつかの選択肢がありました:

デプロイ方法メリットデメリット
Streamlit Community Cloud無料、簡単セットアップ機能制限、セキュリティ制約
AWS/Azure/GCP高い柔軟性、スケーラビリティ複雑な設定、WebSocket対応の課題
Docker + K8s完全な制御、ポータビリティ高い技術的ハードル

Squadbaseの優位性と費用対効果

Squadbaseは、内部AI・データアプリケーション専用に設計されたデプロイメントプラットフォームです。Streamlitアプリケーションの特性を理解した上で、以下の機能を標準提供します:

セキュリティ機能

  • 招待制のセキュアなクラウド環境
  • SAML/OIDC認証の自動設定
  • SSO(シングルサインオン)対応

運用支援機能

  • 実行ログの自動収集
  • ユーザーアクセス解析
  • フィードバック収集機能

開発効率性

  • GitHub連携による継続的デプロイ
  • 設定ファイル(squadbase.yml)による簡単な環境定義
  • 複数のPythonバージョン・パッケージマネージャー対応

環境別デプロイ設定の実践

実際のプロジェクトでは、開発・ステージング・本番環境を分けて管理します。Squadbaseでは、ダッシュボードから環境変数を設定することで、各環境に応じた設定を簡単に行えます。

GitHubとの連携による継続的デプロイ

システム構成アーキテクチャ

効率的な開発フローを実現するため、以下のブランチ戦略と環境構成を推奨します:

環境分離の概念図

データフロー・アーキテクチャ図

開発フロー

  1. 機能開発: feature/new-dashboardブランチで新機能を開発
  2. テスト: ステージング環境で動作確認とユーザーテスト
  3. 本番リリース: プルリクエスト承認後、mainブランチにマージ
  4. 自動デプロイ: Squadbaseが自動的に本番環境を更新

デプロイフロー図

環境別の使い分け

環境用途データアクセス権限
開発機能開発・実験サンプルデータ開発者のみ
ステージング受け入れテスト本番相当データ(個人情報マスク)ステークホルダー
本番実際のサービス提供本番データ全ユーザー

認証・アクセス制御(RBAC)の実装

認証システムの現状と課題

2024年現在、Streamlitは標準でOIDC(OpenID Connect:オープンID接続)による認証をサポートしていますが、認可(どのユーザーがどの機能にアクセスできるか)については外部システムとの連携が必要です。

用語解説

  • 認証(Authentication): 「誰であるか」を確認する仕組み(ログイン)
  • 認可(Authorization): 「何ができるか」を制御する仕組み(権限管理)
  • RBAC(Role-Based Access Control): 役割に基づいたアクセス制御

Squadbaseを活用したRBAC(役割ベースアクセス制御)

SquadbaseのSDK(Software Development Kit:ソフトウェア開発キット)を使用することで、コード内でユーザーの役割に基づいたアクセス制御を実装できます:

import streamlit as st
import squadbase.streamlit as sq

# ユーザー情報の取得
user_info = sq.auth.get_user()
st.write(f"ようこそ、{user_info['firstName']}さん")

# 役割に基づく表示制御
if "admin" in user_info['roles']:
    st.header("管理者向けダッシュボード")
elif "analyst" in user_info['roles']:
    st.header("アナリスト向けダッシュボード")
else:
    st.info("このページを表示する権限がありません。")

ユーザーグループとアクセス権限の設計

効果的なRBACシステムを構築するためには、以下の原則に従います:

  1. 最小権限の原則: ユーザーには必要最小限の権限のみを付与
  2. 役割ベースの設計: 個人ではなく役割に基づいた権限管理
  3. 階層構造の活用: 上位役割が下位役割の権限を包含する設計
役割権限範囲対象ユーザー
viewerデータ閲覧のみ一般ビジネスユーザー
analystデータ分析・フィルタリングデータアナリスト
editorダッシュボード設定変更チームリーダー
admin全機能・ユーザー管理システム管理者

パフォーマンスチューニングの実践

UI分割による効率化:st.fragmentの活用

2024年7月にリリースされたStreamlit 1.37では、st.fragment機能が正式に追加されました。これにより、アプリケーション全体を再実行することなく、特定の部分だけを更新できるようになりました。

@st.fragment
def real_time_metrics():
    """リアルタイム指標の表示"""
    col1, col2, col3 = st.columns(3)
    with col1:
        st.metric("売上", get_daily_sales())
    # 自動更新処理
    time.sleep(5)
    st.rerun()

fragmentとキャッシュの使い分け

  • fragment: 一部分だけを頻繁に更新したい場合
  • cache: 計算コストの高い処理を一度だけ実行したい場合

キャッシュ

Streamlitのキャッシュ機能を効果的に活用することで、アプリケーションのレスポンス性能を大幅に改善できます。適切なキャッシュ戦略により、ユーザー体験の向上とサーバー負荷の軽減を同時に実現できます。

キャッシュの種類と使い分け

1. データキャッシュ(@st.cache_data) データフレーム、リスト、辞書などの一般的なデータオブジェクトに使用します。同じデータを複数の箇所で使い回す際に特に効果を発揮します。

import streamlit as st
import pandas as pd

@st.cache_data(ttl=3600)  # 1時間キャッシュ
def load_sales_data():
    """売上データの読み込み(データベースから)"""
    # 重いデータベース処理
    query = "SELECT * FROM sales WHERE date >= '2024-01-01'"
    return pd.read_sql(query, connection)

@st.cache_data(ttl=1800)  # 30分キャッシュ
def get_aggregated_metrics():
    """集計済み指標の取得"""
    df = load_sales_data()
    return {
        'total_sales': df['amount'].sum(),
        'avg_order': df['amount'].mean(),
        'customer_count': df['customer_id'].nunique()
    }

# 使用例
sales_df = load_sales_data()
metrics = get_aggregated_metrics()

st.dataframe(sales_df)
st.metric("総売上", f{metrics['total_sales']:,.0f}")

2. リソースキャッシュ(@st.cache_resource) データベース接続、機械学習モデル、API接続などの再利用可能なリソースに使用します。データ以外の重いオブジェクトを使い回す際に効果的です。

@st.cache_resource
def init_database_connection():
    """データベース接続の初期化(アプリ起動時に1回のみ)"""
    return create_engine(DATABASE_URL)

@st.cache_resource
def load_ml_model():
    """機械学習モデルの読み込み(メモリに常駐)"""
    return joblib.load('sales_prediction_model.pkl')

# 使用例
conn = init_database_connection()
model = load_ml_model()

キャッシュ設定のベストプラクティス

TTL(Time To Live)の設定指針

# リアルタイム性が重要なデータ(5分キャッシュ)
@st.cache_data(ttl=300)
def get_current_metrics():
    return fetch_real_time_data()

# 日次更新データ(6時間キャッシュ)
@st.cache_data(ttl=21600)
def get_daily_report():
    return generate_daily_summary()

# マスターデータ(24時間キャッシュ)
@st.cache_data(ttl=86400)
def get_product_master():
    return fetch_product_catalog()

キャッシュキーの活用

@st.cache_data
def get_filtered_data(date_range, category, user_id):
    """ユーザーごと、条件ごとに個別キャッシュ"""
    # パラメータが異なれば自動的に別のキャッシュとして保存
    return filter_data(date_range, category, user_id)

# 使用例:異なる条件で個別にキャッシュされる
data_admin = get_filtered_data("2024-01", "electronics", "admin")
data_user1 = get_filtered_data("2024-01", "books", "user1")

実装例:段階的キャッシュ戦略

# レベル1: 生データのキャッシュ
@st.cache_data(ttl=3600)
def load_raw_sales_data():
    return pd.read_sql("SELECT * FROM sales", connection)

# レベル2: 加工データのキャッシュ
@st.cache_data(ttl=1800)
def get_processed_data():
    raw_data = load_raw_sales_data()
    # データクレンジングと基本集計
    processed = raw_data.groupby(['date', 'category']).agg({
        'amount': ['sum', 'mean', 'count']
    }).reset_index()
    return processed

# レベル3: 表示用データのキャッシュ
@st.cache_data(ttl=900)
def get_dashboard_summary():
    processed_data = get_processed_data()
    # 最終的なダッシュボード用サマリー
    return create_summary_table(processed_data)

キャッシュ関連のトラブルシューティング

よくある問題と解決策

# 問題1: キャッシュが効かない
# 原因: 関数の引数が可変オブジェクト(リスト、辞書など)
# 解決策: hash_funcs を使用

@st.cache_data(hash_funcs={list: lambda x: str(sorted(x))})
def process_categories(category_list):
    return analyze_categories(category_list)

# 問題2: メモリ不足
# 原因: 大容量データの長時間キャッシュ
# 解決策: max_entries でキャッシュ数を制限

@st.cache_data(ttl=3600, max_entries=10)
def load_large_dataset(dataset_name):
    return load_massive_data(dataset_name)

# 問題3: 古いデータの表示
# 原因: TTLが長すぎる
# 解決策: 手動キャッシュクリアの実装

if st.button("データを最新化"):
    st.cache_data.clear()
    st.rerun()

この包括的なキャッシュ戦略により、Streamlitアプリケーションの性能を劇的に改善し、快適なユーザー体験を提供できます。

大容量データハンドリング:Polars LazyFrameの活用

従来のPandasに代わり、Polarsを使用することで大容量データの処理性能を向上させることができます。Polarsの利点として以下の点が挙げられます:

  1. メモリ効率: 列指向のデータ構造により、メモリ使用量を削減
  2. 遅延評価: LazyFrameを使用することで、必要な処理のみを実行し、パフォーマンスを最適化
@st.cache_data
def analyze_large_dataset(file_path):
    df = pl.scan_csv(file_path)
    return (df
            .filter(pl.col("amount") > 1000)
            .groupby("category") 
            .agg(pl.col("amount").sum())
            .collect())

データの形式とサーバーの性能によっては、数万行程度のデータ量でもパフォーマンスが落ち込むことがあるので、そのような場合はPolarsを試してみることをお勧めします。

処理優先度の理解と実装

効率的なBIダッシュボードを構築するため、処理の優先度を理解しておきましょう:

処理優先度(高 → 低)

  1. データベース集計: SQLで可能な限り集計処理を行う(最も高速)
  2. サーバーサイド集計: Pythonでの集計処理(中程度の速度)
  3. クライアントサイド集計: ブラウザでの表示・フィルタ処理(最も低速)

パフォーマンス比較例(100万件データ処理)

  • データベース集計: 2秒
  • サーバーサイド集計: 8秒
  • クライアントサイド集計: 30秒以上
# 良い例:データベースで集計
@st.cache_data(ttl=1800)
def get_sales_summary():
    query = "SELECT category, SUM(amount) FROM sales GROUP BY category"
    return pl.read_database(query, connection)

スケーラビリティとデータアーキテクチャ

データマートの検討と実装

アプリケーションが複雑になり、多くのユーザーがアクセスするようになると、リアルタイムでの複雑な集計処理がパフォーマンスボトルネックになります。このような場合、データマートの実装を検討しましょう。

データマート実装の判断基準

  • 同じ集計処理が複数回実行される
  • 集計処理に5秒以上かかる
  • 複数のテーブルを結合する複雑なクエリ
  • 同時アクセスユーザー数が10名を超える

データマート設計パターン

ビジネス要件に応じて適切な設計パターンを選択します:

データマート設計の基本パターン

-- 簡略化されたテーブル構造例
CREATE TABLE sales_fact (
    sale_id INT PRIMARY KEY,
    date_key INT,
    product_key INT, 
    sales_amount DECIMAL
);

この設計により、複雑な分析クエリを高速実行できます。

```python
# データマート更新の基本パターン
def update_sales_mart():
    """売上データマートの日次更新"""
    # 1. 複雑な集計を事前計算
    query = """
    SELECT date, category, SUM(amount) as total_sales
    FROM sales 
    WHERE date >= current_date - 90
    GROUP BY date, category
    """
    
    # 2. 結果をマートテーブルに保存
    save_to_mart(query)
    
    print("データマート更新完了")

# Streamlitアプリでは軽量クエリのみ
@st.cache_data(ttl=1800)
def get_dashboard_data():
    return fetch_from_mart("SELECT * FROM sales_mart")

参考リソース集

公式ドキュメント

コミュニティ

学習教材

まとめ:持続可能なStreamlit BIシステムの構築

本章では、StreamlitアプリケーションをDevOps・DataOpsの観点から本格運用に向けて発展させる方法を学びました。

重要なポイント

  1. 適切なプラットフォーム選択: Squadbaseのような専用プラットフォームを活用することで、認証・監視・デプロイを効率化

  2. パフォーマンス最適化: fragmentとキャッシュを適切に使い分け、データベースでの集計を優先することで高速化

  3. セキュリティとアクセス制御: RBACによる適切な権限管理で、安全性と利便性を両立

  4. 継続的な改善: 運用メトリクスの監視と定期的な見直しにより、システムを進化させる

  5. チーム協力: 非エンジニアとエンジニアの役割分担を明確にし、効果的なコミュニケーションを確立