Google Cloud Functionsを使用したECサイトのクローリング技術について

f:id:crispyblog:20220208115826p:plain

はじめに

バックエンドエンジニアをしています斎藤です。

弊社ではAmazon、楽天、Yahoo!などの各ECサイトでの価格情報やレビュー情報を取り扱っているため、クローリングが必要不可欠になってきています。 しかしユーザーのリクエスト時にクローリングをしてしまうとレスポンスまでに時間がかかってしまい、ページ表示まで時間がかかりユーザービリティを損ないます。 そのためにクローリングの処理はGCPサービスにあるGoogle Cloud Functionsを採用して非同期で情報取得をしています。 今回はそのCloud Functionsの使い方について記してみようと思います。

Cloud Functionsとは

詳しくはググってもらった方がわかりやすいと思いますが、端的な説明をGoogleブログから引用すると

Google Cloud Functions は、サーバー管理なしでコードを実行するスケーラブルな従量課金制 Functions as a Service(FaaS)プラットフォームです。

と言うことらしいです。

サーバー管理なし=サーバーレスになるわけですが、GCP以外のAWS、Azureでも似たようなサービスは存在していますが、弊社はGCPでWebサーバーなどを構築しているため連携の取りやすいCloud Functionsを採用しています。

Google Cloud Functionsの特徴

Google Cloud Fuctions
トリガー - HTTP Request
- Google Cloud Pub/Sub
- Google Cloud Storage
- Cloud Firestore
-Firebase
(Realtime Database, Cloud Storage, アナリティクス, Auth)
- Cloud Logging
用途 - システムとの連携
- サーバーレスでのバックエンドアプリケーション
- リアルタイムデータ処理
- インテリジェントなアプリケーション
(Google AIサービスの利用)

トリガーとしてHTTP Requestや他のGCPサービスのイベントを使うことができます。 HTTP Requestでトリガーを受けることができるのは、どのような開発言語・システムであっても使えるので便利です。 またGCPサービスだと、例えばGoogleCloudStorageで使う場合は、ファイルがアップロードされたタイミングでファイル形式をチェックしてフォルダを振り分けるような使い方ができたり、FirebaseのAuthであればアカウントの作成・削除時に完了・削除メールの配信するシステムを構築することができます。

Cloud Functionsのメリット

  • サーバーレスなのでサーバーの管理などが不要
  • マネージドなのでスケール耐性を意識しないで良い
  • 実行時間でしか課金されないので、単発で動くプログラムなどは頻度によっては安く済む

Cloud Functionsのデメリット

  • 初回起動が遅いので、反応速度が早くなければいけないものなどには向いていない (最小インスタンス設定をすることで改善できます。 https://cloud.google.com/blog/ja/products/serverless/cloud-functions-supports-min-instances)
  • 使用するGCPのトリガーによって複数回呼び出される場合があるため仕様を確認して設計が必要。特に加算処理(カウンターや課金・ポイント処理など)は注意が必要
  • 使える言語は制限がある。(現在はNode.js/Python/Go/Java/.NET/Ruby/PHP)

Cloud Functionsの関数

Cloud Functionsの関数には、HTTP 関数とイベント ドリブン関数の 2 種類があり、さらに、イベント ドリブン関数は Cloud Functions のどのランタイムを対象に記述されているかによって、バックグラウンド関数または CloudEvent 関数のいずれかを使用することができます。 それぞれの関数を使用することで、同期/非同期ともに処理することができるので便利です。

HTTP関数

  • HTTPリクエストをトリガーとする
  • 関数の実行は同期的でレスポンス=関数の実行終了

→トラフィックを処理するために迅速にスケールアップ

バックグラウンド関数

  • Cloud Pub/SubやCloud Storageなどのインフライベントをトリガーとする
  • 関数の実行は非同期的で処理終了の通知はコールバック関数を使用する

→緩やかにスケールアップ

なぜCloud Functionsを選んだのか

弊社の開発言語としてはRailsを使用しているため、一般的な非同期処理のやりかたとしてはSidekiq、Delayed Job、Resqueなどが採用されるかと思いますが、Cloud functions + Cloud Tasksの構成にすることでフルマネージドで稼働ができ、実行時間でしか課金されないためコストが安く済ませることができます。また、開発言語に縛りがないため開発の幅が広がり機能に応じて最適な言語を選択しやすい部分があります。

弊社での使用例

  • 各ECサイトの商品価格および送料情報の取得
  • 各ECサイトの商品レビュー取得
  • ECサイトの商品レビュー分析
  • Lighthouseのレポート送信
  • Webからの画像投稿から不要なEXIF情報の削除
  • GitHubからSlackへのメンション変換

構成図

各ECサイトの商品価格および送料情報の取得の場合

f:id:crispyblog:20220202113142j:plain
商品価格取得の構成図

急にCloud Tasksが登場しましたが、リクエスト制御する上で HTTP関数を使っているため、同期処理になってしまいます。 ここで非同期化するためにCloud Tasksにリクエストを任せてしまいます。 各サイトの運用状況によっては500エラーになることがあるため、そうなってくると即時にリトライしてもエラー解消していないまま再アクセスとなってしまうのでタイマーを設定したいところなのですが、Cloud Functionsだけでタイマー使用するとその間インスタンスが起動しっぱなしになってもったいないです。 エラーを受け付けた場合は一度Cloud Functionsは終了し、Cloud Tasksに再リクエストをすることでタイマー替わりとなり、再度リクエスト処理することが可能です。

Cloud Functionsの使いづらいところ

  • 当初Node.jsの使えるバージョン(v10)が低かったが、どんどん上がるので対応していかないといけない
  • Cloud Loggingにログが出力されるが、出力内容が1行ごとになるのでわかりづらい(Cloud FunctionsというよりLoggingの使いづらいところですね)
  • 時間の経過と共にメモリ利用率が上がりがち
  • 関数呼び出しよりは遅くなってしまうのでリアルタイム性を求められる処理は向いていない

Cloud Functionsのいいところ

  • ローカル環境で動作確認をするための環境が整っている
  • デプロイがローカルマシンからCLI (gcloud コマンドを使用)でもWeb上のCloud Consoleからでも可能
  • オートスケーリングなのでスケーラビリティについて悩む必要がない
  • トリガーの種類が多く、GCPサービスを使用している場合は連携した機能を作りやすい

まとめ

上記のような構成をすることで、ユーザーには意識せずともデータ処理のリクエストを出すことができ、Webサーバー自体は自身のリクエストのみを捌くことに集中することができます。 Cloud Functions稼働によるコスト(Cloud Tasksも)はかかりますが、処理自体は低スペックでも処理できてしまうことがほとんどであるため、Webサーバー単体で動作させるよりはローコストなサーバーレスサービスで処理を実行することができるかと思います。 デメリットでもあげましたが、起動に時間がかかる部分とリクエスト処理をCloud Tasks、Cloud Functionsを経由させている時間もかかってくるため、リアルタイムでデータが必要な処理には向いていないので、適材適所で採用すれば有用かなと思います。

エンジニア募集中!

Cloud FunctionsやECサイトのデータを扱うことに興味がある人は、ぜひWantedlyのページから応募してください! 話を聞くだけでもお待ちしております!