Sansan Builders Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

Serverlessで始める新規サービスのおはなし

こんにちは。Sansan事業部プロダクト開発部でエンジニアをしている 黒澤です。

半年ほど前にSBBに寄稿させていただいたときは、新規事業の立ち上げをしていると書かせていただきましたが、 11月のサービス提供開始と共に、古巣であったSansanプロダクト開発部へと戻ってまいりました。
今回は、この新規事業として作っていた機能で Serverless な機能を作ってみたお話をさせていただこうかと思います。
buildersbox.corp-sansan.com

今となってはかなりServerlessでの事例も増えてきており、それほど目新しさはないのですが、Webエンジニアが、初めてServerlessを採用する際に考えた検討の過程をお伝えできれば幸いです。

はじめに

今回開発した新サービスですが、 「アンケート for セミナー」というサービスです。
これは、アンケートサービス、 CreativeSurvey と連携し、展示会やプライベートセミナーに来場頂いたお客様を対象としてWebアンケートを配信。 その回答結果を Sansanの名刺情報と共に管理することができ、オフラインマーケティングの効率的なデジタル化を目指しているものです。
creativesurvey.com


アンケートの作成、回収は CreativeSurvey を利用していますが、CreativeSurveyにて取得した回答データを 名刺データと共に参照させるためには、SansanへWebhookでデータを送信しています。
今回お話するのはこの Webhookの受信部分となります。

端的に言えば、 Webhookを受け取ってそれをDBに保存する機能です。

求められるシステム特性を整理する

まずは今回作成するWebhook受信機能に求められる性能要件を分析します。

  • Webhookのため受信タイミングが不定期かつコントロール不可
  • アンケートシステムという特性上、アンケートメール配信直後といったアクセスピークが想定される
  • 名刺データとの組み合わせて利用するため現行のRDBへのアクセスが発生する
  • Webhookを受信するだけなのでAPIの応答速度については厳しい要件はない
  • 新規サービスのため事業規模の拡大に伴ってスケールできる
  • 外部サービスとの連携のためイレギュラーな事象があってもデータ復旧が容易であることが望ましい

大きなポイントは以上のようなものでしょうか。
これ以外にも様々な要求事項がありますが、アーキテクチャ選定への影響度が少ないため割愛します。

API Gateway について

Sansan は、 AWS上で動作しているため、 あえて GCP や Azure を採用する選択肢はありませんでした。
(この辺を期待していた方がいたらごめんなさい)
したがって、まず最初に利用することを決めたサービスは API Gatewayになります。
aws.amazon.com


現在では、 あえて Webhook 専用のEC2インスタンスを建てるようなケースはあまりないかとおもいますが、 API Gateway を採用したメリットは以下のような点です。

  • 事業規模の拡大に対応できるスケーラビリティ
  • いざというときも安心な高可用性
  • サーバーのランニングコストの低さ
  • (EC2インスタンスに比べると) 圧倒的にメンテナンスの運用コストが低い

サービスのグロースを伴うアクセスの増加に柔軟に対応できることを前提としながらも、立ち上げ期に余計なコストをかけずに開発したいという、新規サービスにはもってこいのアーキテクチャかと思います。
インフラエンジニアがプロジェクトにいなかったという大人の事情もありますが

ここから先は、実際にアーキテクチャの検討案をお見せしながらどのような理由により最終的な構成を決定したかをご紹介したいと思います。

Design1: Lambda から直接RDBを呼び出す

f:id:ayakakuroron:20200114211100p:plain

まず最初は通常のWebサーバーの機能をそのまま API Gateway + Lambda に置き換えてみたパターンです。

aws.amazon.com

この構成は有名なアンチパターンなのでご存知の方も多いかと思います。

Lambda にはコネクションプールがないため、 実行されるたびにRDBのコネクションを新規に取得することになります。
したがって、Lambdaの同時実行数の数だけコネクションが張られます。
例えば、Lambdaが1000リクエストを同時に受け付けた場合には1000本のコネクションが張られてDBのコネクションが枯渇する可能性があります。
詳しい理由については、 こちらの記事に詳しく解説されております。

www.keisuke69.net


データストアとしてRDBではなく、 DynamoDBを利用することでコネクションプールの問題を回避することは可能ですが、RDBへのアクセスが避けられないため今回の設計では見送りとなりました。

2019年12月の re:Invent で発表された RDS Proxy を利用することで、今後は RDS や Aurora を 利用する場合にはアンチパターンでは無くなる可能性もありますが、開発時には存在しなかったため今回は不採用となりました。次回同様の機能を開発する場合は有力な候補になる可能性もあります。

dev.classmethod.jp

Design2: SQSを利用してPollingする

f:id:ayakakuroron:20200114213854p:plain

Lambda + RDB で発生する問題を回避するために次に考えたのがこちらです。
APIGateway から 直接 SQS に対してQueueを登録することで、 前の案では Lambda で実行するつもりだった RDB へのアクセスを EC2 上で起動するメッセージングサーバーが担当する構成です。
なお、 SansanではすでにSQSからのメッセージング基盤が作成されていたため、低いコストで対応が可能でした。

www.slideshare.net

コネクションプールの問題も回避できており、SQSを利用した分散処理を用いることでアクセスピークが発生した場合も可用性を維持できる構成となっています。
aws.amazon.com



一見よさそうな構成に見えますが、この案には一つ問題がありました。
SQSに送信できるメッセージのサイズ制限です。

Q: Amazon SQS の最大メッセージサイズを設定するにはどうすればよいですか?

最大メッセージサイズを設定するには、コンソールまたは SetQueueAttributes メソッドを使用して MaximumMessageSize 属性を設定します。この属性で、Amazon SQS メッセージに含めることができるバイト数の限度を指定します。1,024 バイト (1 KB)~262,144 バイト (256 KB) の範囲内の値を設定できます。詳細については、Amazon SQS 開発者ガイドの「Amazon SQS メッセージ属性の使用」を参照してください。

よくある質問 - Amazon SQS | AWS

殆どの場合で制限にかかることはないかと思いますが、 今回のWebhookでは、データサイズの上限がなかったためSQSのサイズ制限によりデータ登録できない可能性があります。
現実的にこのようなデータが発生するのかはお客様の運用によって大きく異なるため、開発時点では許容可能な制約なのかの判断ができませんでした。
実際この構成は最後まで悩みましたが、結果的には安全性を重視して不採用となりました。

なお、SQSを採用する場合の注意事項ですが SQSの Standard Queues の場合 Queue の実行は At-Least-Once となります。
つまり、 同一の Queue が2回処理される可能性があるため、冪等性を考慮する必要があります。
https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/standard-queues.html

Design3: S3Eventを利用してデータを登録してみる

f:id:ayakakuroron:20200114220918p:plain

前の構成で問題になった SQS のメッセージサイズ制限を回避するために、一度 S3 にリクエストを保存して、S3Event を経由して SQS に Queue を保存する構成です。
S3で最初にデータを保存することにより、データサイズの制限の問題を回避。
S3にファイルが保存されたことに対する S3Event をキックすることでSQSに Queue を追加。
EC2上に構築されたMessagingServerからSQS上のQueueを取得して、 対象のS3上のファイルへを取得して処理を実行します。

一見良さそうに思えますがこちらの構成にも一つ見過ごせない問題がありました。

S3 Event はまれに Event が消失する場合があります。

イベントは、Amazon S3 イベント通知で通常数秒で配信されますが、1 分以上かかる場合があります。まれにですが、イベントが失われることもあります。

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/NotificationHowTo.html

この問題に対処するには、例えば

  • CloudWatchからイベントの実行漏れがないか定期的にチェックする
  • 対象の処理が行われていなかった場合にも問題なく動作したり、再実行できるような設計にする

といった手段が考えられます。
画像ファイルが保存された場合にサムネイルをS3イベントで生成するといった使い方ではS3Eventを利用するのは効果を発揮しやすい使い方だと思いますが、イベントが消失する前提の設計をした場合に増加する開発コストと比較して判断すべきかなと思います。

発生頻度はかなり低いそうなので、発生した場合に手動で対応するという方法も事業フェーズによっては有力な対処法かと思いますが、イベントの発生漏れを監視するのが困難だったために今回の採用では見送っています。
遅延はともかく最低1回の実行を保証してくれるようになれば採用できなかったシーンでも利用できるのにと強く思っています。

Design4: Lambdaを経由してS3とSQSを利用する

f:id:ayakakuroron:20200115111717p:plain

最終的に採用した構成がこちらになります。
Design3 では、API Gateway から直接S3にファイルを保存していましたが、API Gateway から Lambda を呼び出し、Lambda から S3、SQS への登録を実施します。
これは API Gateway の 統合リクエストに設定できるサービスが1つのみなためです。 複数設定できれば Lambda使わなくても問題ないのに

API Gateway からの Lambda 呼び出しであれば実行が保証されているため、 S3 へのファイル保存、SQSへのQueueの登録を Lambda から実装するパターンです。
今までの構成で発生していた問題点をすべて解決しており、Lambdaを挟むことによって何らかの理由で改修になった場合に拡張性を得ることができました。

欠点としては API Gateway に加えて Lambda + S3 + SQS と依存するサービスが増えてしまっているため可用性は若干落ちることになります。
と言ってもいずれも MultiAZ に対応しているので、 十分な可用性を満たせているかとは思います。

また、Lambdaを採用した場合に、以下のようなデメリットもありますが、このあたりは新規サービスにおける Webhook という用途から考えると、特に問題にならなかったり、メリットの方が大きく上回る内容かと思います。

  • APIへのアクセスが多いとあまりコスト的なメリットがない
  • 長時間アクセスがなかった場合や、スケールアウト時にコールドスタートによりパフォーマンスが悪化する場合がある

さいごに

今回はいろいろ検討した結果、最終的には4番目の Lambda + S3 + SQS の構成を採用しましたが、ほんの少しサービスとしての許容できる制約が異なれば、他の3案のどれを選んでもおかしくなかったと思います。
最近では、AWS の様々なサービスを組み合わせることでオンプレや EC2インスタンスを前提とした構成と比較して容易に環境を構築することができるようになりましたが、エンジニアには各サービスの特性、制限事項を把握した上で効率的なアーキテクチャを考えるスキルが求められているように思います。

各サービスにおけるベストプラクティスなどは様々な先人の方達が書いてくださった有益なblogや、AWSの公式ドキュメントなど参考になるものがたくさんあります。
これらの資料を熟読しながら、実際に環境を構築して試してみることが大切だと改めて実感しました。
ということであまり新規性はなく恥ずかしいのですが、設計する際にはこんなことを考えているという何かの参考になれば幸いです。


buildersbox.corp-sansan.com

buildersbox.corp-sansan.com

buildersbox.corp-sansan.com

© Sansan, Inc.