Dashとは

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)])
Pythonソースコードの例(公式サイトより引用)
描画されたグラフ

グラフの描画種類も様々ある他、ドロップダウンやラジオボタンにスライドバーなど簡単なUIパーツも作ることができるので、パラメータを変えながらリアルタイムにグラフ描画を行うダッシュボード作成も可能です。詳細はDashの公式サイトの説明を参照してください。

Long callbacksを用いた動画データ分析と、そこで生じた問題

Dashには従来から基本的なcallback機能があり、これを用いたサーバサイドでのデータ分析が可能でしたが、2021年9月に公開されたDash 2.0では、新しくLong callbacksという機能が提供されました。Long callbacksを用いると、例えば動画データの解析のように時間のかかる分析処理を手軽に扱えるようになります。

しかし、そもそもWebリクエストでは通常長い処理は想定されていません。そのためDashは別のプロセスとして処理を実行し、ブラウザからは定期的にポーリングするような挙動をします。これにより前後でボタンを無効化したり、進捗を出したりできる他、進捗はSQLiteやRedisに書き込んでやり取りすることもできます。

以下ではACCESSがこのLong callbacksを使い、実際にとある動画データの分析プロジェクトを推進した際に発生したいくつかの問題と対処方法を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負荷が高い状態で発生しやすいようです。

この場合の対処方法としては、一回目のバックグラウンド処理の最後に、出力をデータベースやファイルシステムにも保存するようにします。そして二回目以降の実行では処理の前にデータベースやファイルシステムを確認し、すでに結果があれば返すようにします。