2021.01.12

ちょっとこわいGoogle Cloud Pub/Sub によるFaaS アーキテクチャ

目次


はじめに

あけましておめでとうございます、新卒のY.Cです。この肩書きで投稿するのは最初で最後になりそうです。Google Cloud Functions を使おうとすると大体セットで出てくる Pub/Sub ちゃん、君は誰やねんこっちは定期実行したいだけなんじゃ、とずっと思っていたのですが最近和解できたのでpubsubちゃんの魅力とこわさについて書いてみようと思います。pubsubを知っている人はこわいアーキテクチャまで飛ばしてください。

TL;DR

  1. Cloud Pub/Sub はメッセージ送受信を実現するキュー
  2. push型は処理の分割点、pull型は集約点という見方もできる
  3. メッセージ先を間違えると無限ループするのでこわい

Cloud Pub/Sub 概説

pubsubは一言で言うとただのキューです。では、なぜ定期的にcloud functionsを発火させたいだけなのにキューを経由しなければいけないのか?理由は、疎結合になるとかスケールしやすいとか色々便利だからですね。というかそもそも、Pub/Subメッセージングモデルという概念があり、それをGCPで提供しているのがCloud Pub/Sub というサービスのようです。以下、Cloud Pub/Subについてざっくり説明します。

キュー本体

pubsubの核となるキュー本体を topic と言います。キューでやりとりするデータはメッセージといいます。データ自体は空でも、ある時何かから何かにイベント通知をしたという事実が必要になること(まさにcloud functionsの定期実行など)もよくあります。そういった意味も内包できるよう、メッセージとかメッセージングとかいう言い回しがされていると解釈しています。

送信側

pubsubにメッセージを送る側を publisher と言います。publisherは直接topicに登録して紐づけられるわけではなく、送信時に宛先topicを指定するだけです。様々な言語で書いたプログラムからpublisherとしてメッセージを送信できるのはもちろん、Cloud Schedulerもpublisherとして定期的にメッセージを送信できます。

cloud_scheduler
 

受信側

キューからメッセージを受け取る受信側をsubscriber と言います。subscriber には、キューにメッセージが入ると即時配信されるpush型と、自らキューに溜まったメッセージを取りに行くpull型があります。
subscriber(push)
push型


subscriber(pull)
pull型

アーキテクチャ紹介

ここからはpubsubを用いた簡単なアーキテクチャ例をご紹介します。
    1. 定期実行
      arch1先ほども言及した、functionを定期実行するアーキテクチャです。Cloud Scheduler が定期的にメッセージを発行すると、functionに即座にメッセージがpushされ、それが引き金となりfunctionの実行が開始されます。
    2. 処理の分割
      arch2Cloud Functionsにはタイムアウトやメモリ量、CPUパワーに制限があるため、一度に大量の処理を行うことはできません。そこでpubsubにデータを一件ずつ投げることで、処理を分割します。ここでは、左上のfunctionが大きなデータを1件ずつ読み出し、pubsubに送信します。右下のfunctionはデータ1件を受け取り、それに対する処理を行います。つまり、データがN件あるとき、左上のfunctionは1回、右下のfunctionはN回実行されます。pubsubが処理の分割点になるわけです。このような構成をとることでデータ量に合わせ処理性能もスケールさせることができます。分割処理自体にもマシンパワーが必要になる場合、さらにpubsubを増やすことで分割処理を分割できます。
    3. 処理の集約
      arch3PubSubを一般的なキューとして用いるパターンです。処理を行う対象が何かしらのイベントで、かつリアルタイム性は求められない場合に有効です。イベントをキューに溜めておき、一定件数を取り出してまとめて処理します。つまりpubsubを処理の集約点として扱います。Cloud Functionsの場合、まとめることで実行回数やコールドスタート時間を減少させることでコスト削減になります。

こわいアーキテクチャ

ここからが本題です。以下のアーキテクチャをご覧ください。
arch4
ループしてますね。functionがpush型のsubscriberであり、かつsubscribeしているtopicにメッセージを送信します。これを実際に実行するとどうなるでしょうか?ループします。 複数のpubsubを扱っている中でうっかり送信先を間違えることこのようなことになりかねません。そんなことするわけないと思っていますね?私はしました。しかもfunction内で複数回送信するパターンでした(前述の分割パターン)。これはつまることころ指数関数的に実行回数が増大するというわけです。すぐに気づいたのでノーダメージでしたが、あやうくクラウド死するところでした。皆さんも気をつけてください。

本当に指数関数的に増えるんでしょうか?気になりませんか?試してみました。



もちろん普通にやると破産するので、GCPの無料トライアル3万円分を利用します。せっかくなのでDoS攻撃、もとい負荷試験も兼ねてみることにしました。functionでは、2回メッセージを送信しつつ、GAEに用意したAPIサーバに一回アクセスします。APIが叩かれると、firestore上の数字をインクリメントします<fn>通常1秒間に1回しかfirestoreの同じドキュメントは更新できませんが、インクリメントは結果整合性で正しく処理してくれる模様</fn>。これで正常に処理されたAPIの回数がわかります。GAEは最低性能にしたので当然捌き切れるわけがないのですが、Cloud Functionsで確かにHTTPアクセスをしつつ実行回数を爆発させることを確認したいと思います。無限のクラウドパワーでどこまでいけるでしょうか。夜中の2時半にfunctionを起動し、本当に請求がこないか不安を抱えながら寝ました。


不安すぎて7時に起きて実行状態を確認してみました。ちょっと見辛いですが秒間800回で限界のようです。期待はずれですね… topicだけ削除してループしないようにし、実験を終了することにしました。


…が、なんと実行が収束したのは約5時間後。内部的にある実行キューみたいなものにずっと溜まっていたんでしょうか。もし本当に間違えてループさせた場合、functionごと削除するしかなさそうです。


最終的には、ほぼ3万円分、約2460万回実行されました。億とか兆のオーダーを期待していましたが、短時間でここまで増えるのはやはりちょっとこわいですね。APIサーバが捌けたのは20万回ほどしかありませんでした。

まとめ

pubsubちゃんはFaaSアーキテクチャの柔軟性を高めるすごい子だけど豹変するとこわい。気を付けましょう。

次世代システム研究室では、 Web アプリケーション開発を行うアーキテクトを募集しています。募集職種一覧 からご応募をお待ちしています。




Pocket

関連記事