見出し画像

サービスの成長に伴うアーキテクチャの変更

はじめに


サービスが成長するにつれてどのような課題が発生し対策を実施していくのかを簡単なREST APIを題材に考えてみようと思います。

「あるある」REST API Service


マルチテナントにてリソースの登録、検索、変更、削除を行うためのREST API Serviceを開発し、運用を開始する。

「あるある」Rest APIをOpen APIで定義

1.とりあえずはじめました


課題

サービス開始時は利用状況やサービスの規模が読めないため、スケールの設計が難しい。

戦略

とりあえず、早く安くサービスを構築することにする。
決まったことは以下の通り

  • IaaS(GCP:Compute Engine,AWS: EC2,Azure: Virtual Machine)を利用する。

  • Multi Tenantモデルにてサービスを提供する。

サーバーレスサービス(FaaS)を選択する場面であるかもしれないが、今は横に置いておく。

サービス開始時の構成


2.スケールアップはじめました


課題

Tenant数の増加に伴い、サーバーへのリクエスト数が増大する。
サーバーの負荷が高い状態になった場合、HTTP 5XX系のエラーが発生することになる。

戦略

サーバーのスケールアップを実施しHTTP 5XX系の発生件数を減らす。

ボトルネックとなっている箇所によりスケールアップが必要となるサーバーはまちまちである。

スケールアップを実施した構成の図


3.負荷分散はじめました


課題

爆発的にサービスが成長しテナントが増加すると、スケールアップの限界が近づいてくる。
スケールアップは特効薬だが利用回数は限られているので注意が必要。

スケールアップの限界が見える状態

戦略

サービスを複数立ち上げテナント単位にグルーピングし、グループ毎に使用するサービスにRoutingし、負荷分散を行う。

負荷分散をする実行する


4.スケールアウトはじめました


課題

サービス毎のリクエスト処理能力が見えてくるため、リクエスト処理能力が限界を迎える毎にサービスを構築し運用しなければならず、運用コストは右肩上がりに増加していく。
サーバースペックを常にピーク時に設定して保持しなければならないため、リソースを賢く利用できていない状況になる。

この辺りで人海戦術で継続するべきか、それともアーキテクチャの変更に踏み切るか究極の選択を余儀なくされる状態になる。

戦略

大幅なアーキテクチャ変更を決断した場合、マイクロサービス化を視野に入れるためアーキテクチャ変更を実施する。

  • IaaSの利用からRest Serviceをコンテナ化しサービスクラスタを構成

  • オートスケーリングを導入する

REST Serviceをコンテナ化しClusterを構成する



5.コマンドクエリ責任分離(CQRS)はじめました


課題

リクエストを観測すると、GETとPOST/PUT/DELETEで比較した場合、以下のようなケースになっているのではないでしょうか?
処理時間の平均:GET(read) < POST/PUT/DELETE(write)
リクエストカウント:GET(read) > POST/PUT/DELETE(write)
このような場合、RDSではwriteの処理がreadの性能を劣化する事象が発生します。
また、コンテナの起動が5分以上かかる場合、オートスケーリングの性能が良くない(スケールが間に合わない)などの事象も発生します。

戦略

  • query operationとcommand operationを別のモジュールとしてコンテナを作成する。
    ⇨オートスケーリングの性能向上

  • query operation clusterはRDSのreaderを参照させ、query operation clusterのスケールアウトが発生した場合は、RDS cluster Readerもスケールアウトするようにする。
    ⇨readerのスケールアウトによるquery operationの性能向上

  • command operation clusterはスケールアウトの上限をwriterの性能に合わせて制限し、足りない場合はwriterのスケールアップにより対応する。
    ⇨影響範囲の切り離し

query operationとcommand operationの分離する

6.整合性よりも可用性を重視しはじめました


課題

command operation リクエストが増大するとRDSの書き込み性能の影響をうけるため、RDSのwriterのスペックを常にピーク時に設定して保持しなければならない。
command operation リクエストのスパイクが発生した場合、5XX エラーが発生してしまう可能性がある。

戦略

command operation request受付時にとりあえずqueueに格納する。
RDS(writer)への書込みはconsumerに処理を移譲する。
Rest APIの仕様を変更する。

RDS writerのボトルネックを吸収する

REST APIの仕様変更
/resources (PUT) APIの仕様を変更する

/resourcesの追加(command operation実行対象)

同期処理のAPIは次のようになる

変更前
変更後

変更を行なった場合、REST APIの呼出元への影響が出てしまいます。

まとめ

さて、スモールスタートからはじめて順調にサービスが成長し、それと共に、数々の課題に直面しました。
1~5.まではなんとか外部への影響がない状態で乗り切ることができましたが
とうとう、6.で壁に当たってしまいました。
外部への影響がある場合の対応は、非常に大変です(主に調整が)。
このままでは、RDSの性能を気にしながら、高額なスペックのRDSに縛られることになりそうです。
結論として、Rest APIは最初から非同期アーキテクチャを採用することをお勧めします。

この記事が気に入ったらサポートをしてみませんか?