こんにちは、JS2IIUです。
StreamlitはPythonで手軽にインタラクティブなWebアプリケーションを作成できるフレームワークですが、多人数が同時に利用する状況になると、セッション管理や競合状態に関する課題が浮上します。本記事では、Streamlitの基本構造から、ユーザーセッションの仕組み、そして多人数同時利用時に起きる潜在的な問題点やその解決策を解説します。実践的なコード例も豊富に紹介し、安心してスケーラブルなStreamlitアプリを構築できるようになりますので、ぜひ参考にしてください。今回もよろしくお願いします。
1. はじめに
StreamlitはPythonコードを書くだけで美しいインタラクティブWebアプリを短時間で構築できるオープンソースのフレームワークです。データサイエンスや機械学習のプロトタイピングに広く使われています。
本記事では、特に多人数が同時に利用する状況に焦点を当て、ユーザーごとのセッション管理と競合状態回避の方法を丁寧に解説します。これにより、多人数利用時にも安全かつ効率的にアプリを運用する助けになると思います。
2. Streamlitの基本構造とユーザーセッションの理解
Streamlitの基本的な概念とアーキテクチャ
StreamlitはサーバーサイドでPythonコードを実行し、UIや入力を自動的にブラウザに反映します。各ブラウザのタブは独立したセッションとして管理されていて、Streamlitの状態(変数やウィジェットの値)はこのセッション単位で維持されます。
- セッションごとの状態管理は
st.session_stateで行います - ユーザーの操作はイベントとしてサーバーに伝わり、対応するコードが再実行される仕組みです
ユーザーセッションとは何か、その重要性
ユーザーセッションは、複数ユーザーの状態や入力を隔離し保持する領域です。多人数が同じアプリを利用する際に、各ユーザーの操作やデータが混ざらないようにする不可欠な機能です。
Streamlitではst.session_stateによりセッションごとの状態を保持できますが、アプリの規模やデータの複雑度次第ではより外部のストレージやセッション管理が必要になることもあります。
3. 多人数の同時利用と競合状態の特徴
同時利用時に起こり得る問題
- 状態の競合(競合状態、Race Condition): 複数ユーザーの処理が同時アクセスし、データが壊れるリスク
- セッションの混線: セッション識別が適切でない場合、異なるユーザーのデータが干渉
- パフォーマンスの低下: 共有リソースの争奪による応答遅延やエラー発生
競合状態とは
競合状態とは、複数処理が同時に共有資源やデータを読み書きする際、処理の実行順序やタイミングによって予期しない結果が生まれる状態のことです。これを防ぐためにデータの排他制御や同期機構の導入が必要です。
4. セッション状態の管理:ストレージソリューションとデータ隔離
ストレージソリューションの例と比較
| ストレージ | 特徴 | 利点・適合度 |
|---|---|---|
| st.session_state | Streamlit組み込み、セッション単位で状態管理 | 簡単だがサーバー再起動で消失 |
| ファイルストレージ | 各ユーザーの状態をファイルに保存 | 永続化可能だが排他制御が必要 |
| RedisなどのインメモリDB | 高速で共有データ管理 | 高負荷向け、ロック管理が可能 |
| SQLデータベース | 永続化と複雑なクエリが可能 | 大規模アプリや複雑データ向け |
ユーザーごとのデータ隔離と安全なアクセス方法
- ユーザー識別子を生成する
例:session_idをUUIDで生成し、認証やクッキーと紐付ける - データアクセス時は必ずユーザーIDでフィルタリングを行う
これにより他ユーザーのデータへのアクセスを防止できます。
具体例:セッションID付与とRedisを用いたユーザーデータ管理(簡易版)
import streamlit as st
import redis
import uuid
# Redis接続(localhostの場合)
r = redis.Redis(host='localhost', port=6379, db=0)
# セッションIDを生成・保持
if 'session_id' not in st.session_state:
st.session_state['session_id'] = str(uuid.uuid4())
session_id = st.session_state['session_id']
# ユーザー固有のキーを作成
user_key = f"user_data:{session_id}"
# 入力値を保存
user_input = st.text_input("データを入力してください")
if st.button("保存"):
r.set(user_key, user_input)
st.success("データをRedisに保存しました。")
# 保存データの表示
stored_data = r.get(user_key)
if stored_data:
st.write(f"保存中のデータ: {stored_data.decode('utf-8')}")🔹 使用ライブラリ
import streamlit as st
import redis
import uuidstreamlit: GUI の作成用ライブラリ。Web アプリケーションのインターフェースを簡単に構築できます。redis: Redis サーバーとの接続に使用。uuid: セッションごとにユニークな ID(UUID)を生成するために使用。
🔹 Redis接続の確立
r = redis.Redis(host='localhost', port=6379, db=0)- ローカル環境の Redis サーバー(
localhost:6379)のデータベース0に接続。 rは Redis クライアントオブジェクトです。
🔹 セッション ID の生成と保持
if 'session_id' not in st.session_state:
st.session_state['session_id'] = str(uuid.uuid4())
session_id = st.session_state['session_id']st.session_stateは Streamlit が提供するセッションごとの状態管理用の辞書です。- セッション開始時に一度だけ UUID を生成し、以降の処理では同じ ID を使い続けるようにします。
- これにより、ユーザーごとに異なるデータを保存できるようになります。
🔹 Redis 用のキーを生成
user_key = f"user_data:{session_id}"- 各ユーザーのセッション ID に基づいて、Redis に保存する際のキーを生成しています。
- Redis 側では、例えば
user_data:0d123...のような形式でキーが作られます。
🔹 入力フォームの作成
user_input = st.text_input("データを入力してください")- ユーザーが任意のテキストを入力できる入力欄を表示します。
- 入力値は
user_inputに格納されます。
🔹 入力値を保存(ボタン操作)
if st.button("保存"):
r.set(user_key, user_input)
st.success("データをRedisに保存しました。")- 「保存」ボタンが押されると、Redis にユーザー固有のキーでデータを保存します。
- 成功メッセージを表示します。
🔹 保存済みデータの表示
stored_data = r.get(user_key)
if stored_data:
st.write(f"保存中のデータ: {stored_data.decode('utf-8')}")- Redis に保存されたデータを
r.get()で取得します。 - データはバイト列で返されるため、
decode('utf-8')で文字列に変換します。 - ユーザーに保存されたデータを表示します。
Redisについて
Redis(REmote DIctionary Server)は、主に インメモリ(メモリ上)で動作するキー・バリュー型のデータストア(NoSQLデータベース)です。
🔸 Redisの概要
| 項目 | 内容 |
|---|---|
| 種類 | NoSQL型のインメモリデータベース |
| 主な用途 | キャッシュ、セッション管理、キュー処理、リアルタイム分析 など |
| データ構造 | 文字列、リスト、ハッシュ、セット、ソート済みセットなど |
| 通信プロトコル | TCP/IP(デフォルトポート:6379) |
| データ永続化 | オプションで可能(RDBやAOFなど) |
🔸 Redisの主な特徴
✅ 1. インメモリ型データベース
- すべてのデータをメモリ上に格納するため、非常に高速。
- その代わり、メモリ容量が限界となるため、大量データの保存には向かない。
✅ 2. 多様なデータ構造に対応
- 単なるキー・バリューだけでなく、以下のような構造をネイティブで扱える。
- Strings(文字列)
- Lists(順序付きリスト)
- Hashes(連想配列、辞書型)
- Sets(重複なしの集合)
- Sorted Sets(スコア付きの集合)
- Bitmaps, HyperLogLogs, Streams なども対応
✅ 3. 永続化(オプション)
- 通常はメモリ上のデータベースですが、以下の方法でディスクへの保存も可能です。
- RDB方式(スナップショット)
- AOF方式(変更ログを記録)
✅ 4. シンプルなインターフェース
- CLI(コマンドライン)で簡単に操作できます。
- 例:
SET mykey "hello"、GET mykey
✅ 5. 高速でスケーラブル
- 単一インスタンスでも高速。
- レプリケーション、クラスタリング、シャーディングなどにも対応しており、スケーラビリティも高い。
🔸 Redisの主な用途例
| 用途 | 説明 |
|---|---|
| キャッシュ | DBのクエリ結果やAPIレスポンスを一時保存して高速化 |
| セッション管理 | Webアプリでユーザーのセッション情報を管理 |
| メッセージキュー | リスト構造を活用して非同期処理のキューとして使える |
| リアルタイムランキング | Sorted Set でランキングの管理 |
| Pub/Sub | 発行・購読モデルのメッセージ送信機能 |
🔸 簡単なコマンド例(Redis CLI)
# 起動
redis-server
# クライアント接続
redis-cli
# コマンド例
SET username "chatgpt"
GET username
DEL username🔸 利用に適したケース
- 高速性が求められる処理
- 一時的なデータの保存(例:キャッシュやセッション)
- データの整合性よりもパフォーマンスが重視されるシステム
🔸 利用に注意が必要なケース
- 長期間のデータ保存(メモリ消費が大きくなる)
- 高いデータ整合性が求められる金融系システムなど
🔸 補足
Redis は Python や Node.js、Java など多くの言語にクライアントライブラリがあります。Python では redis-py が主に使われています。
5. 競合状態の回避:ロック機構、同期手法、キャッシング戦略
ロック機構と同期手法
Redisではシンプルなロック実装として SETNXによる排他制御が使えます。
lock_key = f"lock:{session_id}"
import time
def acquire_lock(redis_client, key, timeout=5):
lock_acquired = redis_client.set(name=key, value="locked", nx=True, ex=timeout)
return lock_acquired
def release_lock(redis_client, key):
redis_client.delete(key)
# ロック取得を試みる例
if acquire_lock(r, lock_key):
try:
# クリティカルセクション (共有リソースの更新処理)
# ここに処理を記述
pass
finally:
release_lock(r, lock_key)
else:
st.warning("現在他の処理が実行中です。しばらく待って再度お試しください。")キャッシング戦略の実装例
Streamlitの@st.cache_dataや@st.cache_resourceを活用し、頻繁に変わらない計算やデータ取得を効率化します。
@st.cache_data(ttl=60)
def expensive_computation(param):
# 高コストな計算
time.sleep(3) # 処理時間のシミュレーション
return param * 2
result = expensive_computation(10)
st.write(f"計算結果:{result}")これにより無駄な計算を減らし、負荷と競合リスクを低減できます。
6. トラブルシューティングとよくある問題の解決方法
| 問題 | 原因例 | 対処法 |
|---|---|---|
| セッションの状態が共有されてしまう | セッションIDが適切に管理されていない | st.session_stateの利用、適切な認証によるユーザー識別 |
| 複数ユーザーが同時にデータを書き換える | 競合状態(ロックや排他制御なし) | RedisロックやDBのトランザクション利用 |
| キャッシュが古いまま更新されない | キャッシュのTTL設定不足 | 適切なキャッシュ期限設定や手動クリア |
| パフォーマンスが著しく低下する | 重い処理を同期的に実行している | キャッシュ活用、非同期処理や分散処理の導入 |
7. まとめ
本記事ではStreamlitの基本概念から始まり、多人数が同時にアプリを利用する際のセッション管理や競合状態の課題について詳しく解説しました。また、Redisやファイルストレージの活用例、ロック機構やキャッシングの実装方法、さらにトラブルシューティングのノウハウも紹介しました。
多人数対応の基盤を整えることで、より堅牢かつ拡張性の高いStreamlitアプリを構築できます。ぜひ実際にコードを試しながら、自分の用途に最適なセッション管理手法を検討してみてください。
参考
最後までお読みいただき、ありがとうございました。


コメント