- Contents -
Dashは主にデータの可視化に用いられるPythonのWebフレームワークです。
データサイエンス・AIのプロジェクトにおいては、複雑なWeb UIを提供することよりシンプルなUIやデータの可視化が求められることが多く、ACCESSでもこのフレームワークを使ってプロジェクトを推進することがあります。
Dashフレームワークを使うと、例えばこのようなPythonコードを描くだけでグラフ描画をする事ができ、データサイエンス・AIで多用されるPythonとその周辺ライブラリとの相性が非常に良く、別途ダッシュボードなどのフロントエンドを開発することなく低コストでWeb画面上に可視化して顧客に提供する事が可能となります。
df = pd.DataFrame({ "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"], "Amount": [4, 1, 2, 2, 4, 5], "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"]})
fig = px.bar(df, x="Fruit", y="Amount", color="City", barmode="group")
app.layout = html.Div(children=[ html.H1(children='Hello Dash'), html.Div(children='Dash: A web application framework for your data.'), dcc.Graph(id='example-graph', figure=fig)])
グラフの描画種類も様々ある他、ドロップダウンやラジオボタンにスライドバーなど簡単なUIパーツも作る事ができるので、パラメータを変えながらリアルタイムにグラフ描画を行うダッシュボード作成も可能です。詳細は公式サイトの説明を参照してください。
Dashには従来から基本的なcallback機能があり、これを用いたサーバサイドでのデータ分析が可能でしたが、2021年9月に公開されたDash 2.0では、新しくLong callbacksという機能が提供されました。Long callbacks を用いると、例えば動画データの解析のように時間のかかる分析処理を手軽に扱えるようになります。
しかし、そもそもWebリクエストでは通常、長い処理は想定されていません。そのため、Dashは別のプロセスとして処理を実行し、ブラウザからは定期的にポーリングするような挙動をします。これにより前後でボタンを無効化したり、進捗を出したりできる他、進捗は SQLite や Redis に書き込んでやり取りする事もできます。
以下ではACCESSがこのLong callkacksを使い、実際にとある動画データの分析プロジェクトを推進した際に発生したいくつかの問題と対処方法をTipsにしました。
long_callbackデコレータで修飾された関数は別プロセスで実行されます。ここで気をつけなくて行けないのは、別プロセスとしてPythonが新しく実行開始するという点です。dashサーバーを起動するときにimportされるファイルの中で初期化処理をしている場合、このバックグラウンドジョブの開始時にも同じ初期化処理が実行されます。そのため例えばキャッシュの削除を起動時に行うと、バックグラウンドジョブの開始のたびにキャッシュの削除が行われてしまいます。
この場合の対処方法としては、if __name__ == “__main__”: の中にサーバーの起動処理を書きますが、同様にこのif文の中でキャッシュの削除などを行うようにします。上記のif文での実行が難しかったり面倒だったりする場合は、環境変数を利用する方法もあります。我々は以下のコードでキャッシュディレクトリを削除しました。
cache_path = ...
if os.environ.get("DASH_CACHE_DIR") != cache_path:
if os.path.exists(cache_path):
shutil.rmtree(cache_path)
os.environ["DASH_CACHE_DIR"] = cache_path
環境変数は基本的に子プロセスに引き継がれるので、このようなコードを書くことでサーバー起動時にのみ実行させることができます。
Long callbackではコールバック関数の実行開始の後に、プログレスバーを表示するために定期的にdashによって定義されたcallback関数が呼ばれています。問題はこのcallback関数の入力にlong_callbackで指定したものが引き継がれることです。
例えば動画を解析したい場合は Upload要素のcontentをInputとして受け付けるlong_callback関数を定義しますが、上記の挙動のため、毎秒のポーリングの度に動画ファイルがサーバーにアップロードされます。その結果プログレスバーのレスポンスが極めて悪くなります。
この場合の対処方法としては、ファイルのアップロードと重い処理のcallbackを分けるようにします。ファイルをアップロードする際はサーバーのファイルシステム(またはデータベースなど)に保存し、そのパスやIDをOutputとして返します。解析処理のcallback関数は、アップロード関数でOutputで指定された要素をInputとして指定します。ファイルパスやIDは十分に軽いので、毎秒のポーリングの処理は遅延なく動作するようになります。
これは Long callback の既知バグで、GitHubのissueでも何件か報告されています。現在、バックグラウンドタスクの終了判定処理コードに問題があり、ポーリングの途中のタイミングでバックグラウンド処理が終了した場合、強制終了と勘違いし、再実行されてしまいます。
この問題は特にCPU負荷が高い状態で発生しやすいようです。
この場合の対処方法としては、一回目のバックグラウンド処理の最後に、出力をデータベースやファイルシステムにも保存するようにします。そして二回目以降の実行では処理の前にデータベースやファイルシステムを確認し、すでに結果があれば返すようにします。