GCP を使って名刺管理ボットを本気で作ってみた

2023-08-15

タイムアウト問題

ここまでに説明してきた Web App Bot – Dialogflow – Vision API – Elasticsearch の説明で名刺管理ボットを実装するためのポイントが分かったと思います。一通りの流れが完成した後は、最新の名刺だけ表示させたり、同じ人の過去の名刺も表示させたり、また、他人が登録した名刺を表示する機能など、実際に使ってみて必要となった機能を追加していきました。

ただ、実運用するには致命的な問題がありました。それは頻繁にタイムアウトでエラーが発生してしまうという問題です。この問題の要因は2つあり、一つは Dialogflow のタイムアウト制限、もう一つは Cloud Functions 等のサーバレスアーキテクチャを使用している点でした。

Dialogflow タイムアウト制限

Dialogflow から呼び出された Webhook は 5 秒以内にレスポンスを返さないとタイムアウトが発生する制約があります。agent.setFollowupEvent を使用して Dialogflow と Webhook 間を内部的にやり取りさせながらタイムアウトまでの時間を延ばす手法もある様ですが、ボットの使い心地を考えれば出来れば 5 秒以内にレスポンスを返すようチューニングしたいものです。

名刺管理ボットでは Dialogflow から Cloud Functions に実装した名刺管理アプリを呼び出します。名刺管理アプリは Cloud Storage との画像のやり取りや Vision API による OCR の実行、サムネイル画像作成の為の画像加工、Elasticsearch への登録や参照など各種処理を行います。

とは言え、これら処理が常にタイムアウトする訳ではなく、正常終了する場合はほとんどの場合 1 秒に満たない処理時間でした。タイムアウトが発生するのは、名刺管理アプリが久しぶりに呼び出された場合に発生しました。

この問題は名刺管理アプリが Cloud Functions である事に起因します。

サーバレスアーキテクチャ Cloud Functions のコールドスタート

Cloud Functions はサーバレスアーキテクチャと言い、開発者がサーバや OS、ミドルウェアを意識せずソースコードを書いて呼び出すだけで関数を実行できる仕組みです。関数が呼び出されると裏では実行環境に関数のインスタンスが生成されリクエストが処理されます。

ただしこのインスタンスは毎回ゼロから生成されるのではなく、継続的に呼び出されていると前に使用されたインスタンスが再利用される為、その際はグローバル領域に定義されたライブラリの読み込みや初期化処理等が不要になります。

逆に言うとしばらく利用されていないインスタンスは消滅してしまうので、次回呼び出し時はゼロからのインスタンス生成となります。これをコールドスタートと呼びます。そして今回の名刺管理ボットは、当社で使用する限りではそれほど頻繁に呼び出されないと思われる為、このコールドスタートが頻繁に発生する事が推測できました。

コールドスタート時、インスタンス生成自体は 1 秒とかからない処理ですが、今回の名刺管理アプリの様に多くのライブラリを使用しているとその読み込みと初期化に時間がかかります。毎回全てのライブラリを読み込まない様にする遅延初期化する方法もありますが、常に使用する Dialogflow の Webhook クライアントライブラリ自体がその読み込みだけで 4 秒近くかかるときがあるくらい重く、あまり効果的ではありませんでした。

結局、コールドスタート発生時には毎回 Dialogflow のタイムアウトが発生するという問題が残りました。

Cloud Functions は呼び出された回数と使用したリソース量に応じて料金が決まる為、頻繁に使用されないアプリを公開する場合、常にサーバを設置すのに比べ非常にコストパフォーマンスに優れています。ただしその場合はコールドスタートが発生する事も念頭に置いておく必要があります。久しぶりに呼び出されたときにも高レスポンスが必要となるアプリの場合は一考が必要です。

Azure Bot Service Web App Bot もサーバレスアーキテクチャ

Teams からメッセージを受け取る役目の Web App Bot もサーバレスアーキテクチャです。ボットは Cloud Functions の Azure 版にあたる Azure Functions 上で実行されます。従って、久しぶりの呼び出し時にはその起動に時間がかかります。今回は無料プランを使用しているせいなのかとても時間がかかります。Cloud Functions は数秒の劣化ですが、Web App Bot の場合は 1 分以上待たされる事もあります。

しかし Web App Bot の場合はボットの料金プランを Basic 以上にすると「常時接続」というオプションがあり、これをオンにすることでボットのインスタンスをアンロードされることなく保つことが出来ます。

設定は 構成 > 全般設定 > 常時接続 から行います。

この図は、無料プランの為「常時接続」はオフのまま非活性で選べません。

対策

社内利用であれば久しぶりに使用するとタイムアウトするというのも許容できなくはないのですが、実用化レベルを目指していたので対応する事としました。タイムアウトするのは Dialogflow から呼び出される Cloud Functions 上の名刺管理アプリですが、Web App Bot 上で動くチャットボットアプリも実用には耐えられないくらいレスポンスが悪い時がある為、併せて対応する事としました。

対応策としてこの2つを Google Kubernetes Engin(GKE)上へ移行し常時稼働する通常の Web サービスとして再構築する事にしました。処理を分割したり、常時接続設定をオンにしたりといった方法もありますが、今回は共に GKE へ移行する事にしました。アプリを Azure と GCP に分散させておくのは非効率ですし、一括して GKE 上で管理すれば効率的と考えました。既に構築済みの GKE 環境を持っていたことも大きな要因です。

Cloud Functions から GKE へ、Web App Bot から GKE と2つの移行作業があります。次ではその方法を説明します。