新しいシステム開発の地図帳

[Ateam Group Manager ＆ Specialist Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ateam) の6日目は
株式会社エイチームフィナジー 金融メディア事業部の村上が担当します。
バックエンドの開発をメインでやっています。

## はじめに
今年の1月にエイチームフィナジーにジョインさせていただきました。
エイチームとしては新米ですが、業界歴としては長く今年で15年になります。
これだけ長くやっているといろんなシステム構築を行っており、それの経験をもとに、今後重要となってくるであろうシステムアーキテクチャについて少し書かせていただきたいと思います。


## コントロールプレーンとデータプレーン
いきなり難しい単語ですいません。。。

これは何かというとKubernetesのアーキテクチャーの一つです。
「Kubernetes使ってないから関係ないやん！！」とは思わないでください。

簡単に説明していきます。

### CMSを例に
「Contents Management System：コンテンツ・マネジメント・システム」を例にあげます。
ブログ・サイトやメディア系のサイトをイメージして見てください。

機能として、
- 管理者が記事投稿する
- ユーザーが記事を閲覧する
- ユーザーの閲覧データを取得する
といった機能が挙げられると思います。

これらは2つは簡単に「管理機能」と「ユーザー機能」に分けることができます。
この2つを別のサービスに分けて、管理しようというのが基本的な発想です。

- 管理機能:コントロールプレーン
- ユーザー機能：データプレーン

という感じです。

1つのアプリケーションに2つの機能(管理機能とユーザー機能)を実装して、サーバーやコンテナで動かしたり、1つのアプリケーションではあるけど、パス(/admin)とそれ以外でフォワードするサーバーを分けたりするということをすることがあると思います。
![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17939/bf8ed1cb-cb87-b171-7593-b4eec33358e8.png)

今回の場合は、アプリケーションを分けて、更に永続化層(DBなど)やリソースも分けるということまでします。
こうすることで得られるメリットに以下のようなことが挙げられます。
![Figure_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17939/6e2c831a-203d-7c99-5228-b4ebe70e3267.png)


### 対障害性が高くなる
可用性を考慮して、サーバーを複数台にすることがありますが、DBなど、共有している部分が落ちてしまうとサービスが停止してしまします。
CMSを例にすると、閲覧などのユーザー機能では、記事データをHTMLで、S3のようなフルマネージドなオブジェクトストレージにあげておけば、DBサーバーがダウンしていても、ユーザー機能には影響なく、サービスが継続可能です。

### 定期メンテナンスが容易
データベースはデータの読出し、書込みには非常に優れている反面、意外と手のかかるものです。

例えば、
- データバックアップ
- セキュリティパッチ
- バージョンアップ
など、

上記作業のたびに、サービスを一時停止させざるを得ない場合もあります。
しかし、DB参照を管理機能のみに集中させていた場合、限定的なサービスの一時停止のみで容易にメンテナンスが実施可能になります。

### デメリット
デメリットももちろんあります。

- 分割しすぎると、ソースの管理が大変
    - サービス分割は適切に
- サーバー側はコンテナ化していないと、管理が大変
    - コンテナ化はほぼ必須です
- リソースは最適化していないと費用が大変
    - DBをサービスごとに作成したりすると費用がかさむので、そのへんの最適化は必要です。
    - しかし、統合してしまうと、分割している意味がなくなる

などです。

## データベース以外の選択肢
上記でDBは運用保守にそれなりのコストがかかります。
データサイズが膨らめば膨らむほどそのコストが重くのしかかります。

できれば、スケールが自動でメンテナンスコストがかからないフルマネージドなものを採用したいです。
そして、フルマネージドなものでユーザー機能を構成するとダウンタイムがないサービスが構築できます。

データストアやストレージは以前よりも選択肢としてはかなり多くなっているので、用途別に簡単にまとめていきます。

### Key-Valueデータベース
AWSでフルマネージドなKey-Valueデータベースと言えば「DynamoDB」です。
最近ではキャパシティを自動でスケールする「オンデマンドキャパシティ」があり、読込み/書込みリクエストを計算してキャパシティ設定する必要もなくなりました。
厳密なトランザクション管理を必要とせず、複雑なクエリー処理を必要としないものに向いています。
よくセッション管理などが例で挙げられたりします。

また、DynamoDBは後述するストリーミングにも対応しています。

### オブジェクトストレージ
AWSでいうS3です。
たぶん説明がいらないほどだと思いますが、一昔前はストレージサーバーを立てて、NFSでマウントというやり方でしたが、今はRestAPIでリソースが管理できるオブジェクトストレージが一般的です。
AWSのS3は耐久性はイレブンナイン(99.999999999％)なのは有名だと思います。

HTMLファイルを上げれば、ホスティングも可能のため、コンテンツを見せるだけのWebサーバーとしても活用できます。

S3も後述するストリーミングにも対応しています。

## データ連携は非同期処理
管理機能であるコントロールプレーンは、機能性を重視し、システムによるダウンタイムをある程度考慮した設計にする。
ユーザー機能である、データプレーンは、フルマネージドなサービスを組み合わせて、自動でスケールアウト/インする仕組みにしておき、ある程度、機能では制限を行う

ここで問題になるのが、管理機能とユーザー機能でどうやってデータを同期させるかです。
管理機能で編集・保存された記事データ(DB)をユーザー機能側へ保存する(S3)必要があります。

また、逆にユーザーが閲覧するサイトにタグのようなWebビーコンを設定している場合、大量のリクエストを受け付けて処理させるサーバーが必要になります。そして、そのデータを管理機能側へレポートデータとして同期させたりする必要もあると思います。

ユーザー機能側でAPIを作成して、それを実行するような機能も考えられなくはないですが、そうすると管理機能側でユーザー機能に依存した処理が混入してしまします。

せっかくサービスとして分離したので、できれば依存した処理を混入はしたくない

そういった場合には、処理するデータを一旦別のデータ領域に送信して、受け取る側がデータ領域を参照して処理をさせます。

以下のようなクラウドの仕組みを使いデータを非同期で処理させます。

### メッセージキューイング(MQ)
非同期処理をするのに一般的なやりかたです。
APIのように直接データを渡すのではなく、MQを用意して、間接的にデータのやりとりを行います。
AWSではSQSになります。

SQSのメッセージングをポーリングする必要がありましたが、最近ではLambdaと連携させてそのあたりの管理をしなくてもよくなりました。

完全マネージドで動作するし、並列処理にも向いています。
しかし、メッセージのデータサイズには制限があるので、メッセージが大きい場合はS3に保存するなど工夫が必要です。

### ストリーミング
大量のデータ(ストリーミングデータ)を処理したい場合には、それを処理できる基盤が必要になります。
AWSではKinesisになります。

IoTのようなデバイスセンサーから送られてくるようなデータであったり、サイトに仕込んであるWebビーコンなどを処理させたい場合はこちらが向いています。

完全マネージドで、メッセージにサイズもSQSに比べると大きいです。
しかし、キューイングとは異なり、データは残り続けるので、どこまでデータを処理したかなどの状態を持っておく必要があります。
KCL(KinesisClientLibrary)やLambdaを使うとそのあたりは自動でやってくれます。

もう1つの利点として、受信側を複数設定可能です。
SQSに関してはメッセージ処理するとキューから消えてしまいますが、Kinesisはデータを保持しているので複数の連携処理を設定できます。
Webビーコンのストリームデータを1つは管理機能のレポートへ、1つはアクセス解析する外部サービスへ連携などが容易に可能です。

また、Kinesisだけではなく、DynamoDBやS3もストリーミング処理が可能です。
DynamoDBにデータが保存された時や、S3にファイルがアップロードされた時など、Lambdaで受け取り処理することが可能です。

### メッセージング
単純なメッセージング処理というのもあります。
AWSでいうSNSになります。
Lambdaと連携させることが可能です。
SQSやKinesisに比べて簡易ですが、データを保存する場所がないので、処理に失敗するとリトライが難しいのが難点です。

### データを非同期処理する際の注意点
データを非同期で反映させるにはいくつかの注意点があります。

#### 1. データの順番に依存しないようにする
キューイングしたり、受信側を並列化したり、Kinesisでシャード数を増やすとデータを送信した順に受信はしなくなります。
順序制御したいときは別の仕組みで担保する必要があります。

#### 2. 重複実行の制御
非同期処理の場合、同じデータを2回処理してしまう場合もあります。
マネージドな部分もあり、こちらで制御するのが難しい場合もあるので、重複実行の制御をアプリ側で制御させる必要があります。
よく使われるものとして、「処理ID」と言われるUUIDなどを発行して、同じUUIDは実行しないなどの制御をします。

#### 3. エラー再試行とエクスポネンシャルバックオフ
非同期処理に限ったことではないですが、いろいろな処理をAPIや非同期処理が増えてくると、処理失敗することもあります。
一時的なエラーであれば、再実行する仕組みを作っておけば問題ないのですが、継続的なエラーの場合、即時に再実行されるとサーバー側の負荷が上がっていきます。
これを防ぐために、再実行時には適度な遅延間隔を設ける必要があります。
よく使われるのは「エクスポネンシャルバックオフアルゴリズム」です。
簡単に言うと、再実行の間隔を1秒後,2秒後,4秒後,8秒後とだんだん増やして行きます。
こうすることで適度に間隔があくので、受信側のサーバーの負荷が爆発的に上がることを抑えることができます。

#### 4. サーバーレスで
AWSだとLambdaを使用すればサーバーレスで連携部分を構築可能です。
サーバーレスは管理コストが下がるので非常に便利なものですが、運用には若干の工夫がいります。
Lambdaを多用すると、「どこで何が動いているかわからない」「どれとどれが連携しているかわからない」という状況に陥ります。
Lambdaでシステム構築するときは、コード管理はもちろん、連携するサービス(SQSやKinesis)も管理するのが良いと思います。

## まとめ
上記のことをまとめると下記のようなシステムアーキテクチャ図になります。

![Figure_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17939/68c5f6f4-d4f5-a4e1-eeed-aa8105c2d7aa.png)


* 管理機能とユーザー機能で完全に永続下層まで分ける
* ユーザー機能部分はクラウドのフルマネージドのサービスで構築する
* データ連携はLambdaを使ったサーバーレスで構築する

がポイントになります。

もちろんこれにはデメリットもあります。

* 初期構築時にはある程度の工数が必要
    * フルマネージドやサーバーレスで運用は楽になるかもしれませんが、構築コストはある程度増える
    * 新しい技術を取り入れる時はある程度のバッファを考慮しておく必要がある
* 全体を管理する仕組みが必要
    * 何度か触れましたが、いろいろなもので構築するので、その管理は雑になりやすい
    * 導入・構築の際にはツールやルールを設けておく
* クラウドのマネージド・サービスに慣れる
    * 権限関連でエラーを起こす
    * ログが見にくい
    * どこの連携が失敗しているかわからない
    * などが起こるので、運用経験を積む必要がある

少し難しくとっつきにくい内容になったかもしれませんが、新しくアーキテクチャからシステムを開発することがあるのであれば、参考になれればと思いアドベントカレンダーに書かせていただきました。