【Tech02】コズレを支える技術 アプリケーション構成編
こんにちは。コズレのもっちーです。
最近、この界隈ではマイクロサービスにしようだのやっぱりモノリシックに戻ったほうがよいだのと盛り上がっているようですが、今回はアプリケーション構成の観点で、コズレはどう取り組んでいるのか、整理してみたいと思います。
アプリケーションの構成
マイクロサービスアーキテクチャとは
かの有名なエンジニアのマーチン・ファウラー氏らによって、数年前に以下のようなことが言われていました。
単一のアプリケーションを小さなサービスのスイートとして開発するアプローチであり、それぞれが独自のプロセスで実行され、軽量メカニズム(多くの場合、HTTPリソースAPI)と通信します。これらのサービスはビジネス機能を中心に構築されており、完全に自動化された展開機構によって個別に展開できます。これらのサービスの最小限の集中管理があり、異なるプログラミング言語で記述され、異なるデータストレージテクノロジーを使用する場合があります。 (翻訳 by Google)
出典:https://martinfowler.com/articles/microservices.html
モノリシック vs. マイクロサービス
図に示したのが、いわゆるモノリシックアーキテクチャとマイクロサービスアーキテクチャを模式化したものです。
左側がモノリシックの図で、プレゼンテーション層、ビジネスロジック層、データアクセス層の3層モデルの想定で記述しています。 対する右側がマイクロサービスの図で、プレゼンテーション層の代わりに、各サービスを束ねて、クライアントとのやり取りを担うAPIゲートウェイがあり、ビジネスロジックにあたるマイクロサービスが処理の単位ごとに独立して存在し、データベースも独立して存在しています。
詳しい解説は、ぐぐってもらえばよいかと思うので割愛します。 この構成を踏まえて、コズレではどうなっているのかというところを深堀していきたいと思います。
コズレはミニサービス
ミニサービスのモデル
マイクロより大きいので勝手にミニサービスとつけましたが、図に示したものがコズレでのケースをモデルにしたものです。
いつか、Wikipediaにミニサービスとはエンジニアもっちーが最初に言い出したと記述されると思います。
サブシステム x 内部API x MQ
コズレのサービスは、マガジンサービス、クチコミサービス、ユーザ基盤、分析基盤、メルマガツール等、複数のサブシステムの組み合わせで成り立っています。
コズレでは、Java言語で開発をしているので、WEBアプリケーションならば war ファイル、バッチ処理系アプリケーションならば jar ファイルがリリースする時のシステムの単位になります。
そして、この war(jar) の単位がサブシステムとなっています。 マガジンサービスなら magazine.war があり、ユーザ基盤なら user.war があります。 そして、前回の「システム構成編」でも紹介していますが、内部APIがあるので、ユーザAPIなら、api-user.war があり、マーケティングAPIなら api-marketing.war があるのです。
そして、それぞれのサブシステムや内部APIから呼び出されるメッセージキューイングの処理は jar として複数存在しています。 画像のリサイズをしてアップロードする処理なら magazine-edit.jar 、メルマガを配信する処理なら mail.jar といった具合です。 (正確には jar の中にはメッセージを投げるパブリッシャーやメッセージを受け取るサブスクライバーがいて、キューをうけとるスレッドワーカーがコンシュマーとして各処理を担当しています)
こう見ていくと、コズレってマイクロサービスちゃうか?ってミルクボーイも言いそうですが、そこまでマイクロサービスじゃないのです。
トランザクション境界の問題
コズレではサブシステムとほぼ対になる形でデータベースが存在しています。 マガジンサービスなら magazine のDBがあり、ユーザ基盤なら user のDBがあり、メルマガツールなら mail のDBがあります。
マイクロサービスでは処理を分割しすぎるせいでトランザクション境界が問題になることがありますが、コズレのサブシステムではDBがそのまま関心事の対象で定義してあり、サブシステム内で閉じています。 トランザクションが分離されてロールバックできないとか、整合性が崩れるといったことがありません。
小さすぎず、モノリシックでもないので、ミニサービスなのです。
分割の基準は何か?
サブシステムとして設計する時に、どう世界を切り取るのかが肝かもしれないと思います。 ドメイン駆動開発の思想で関心事をまとめると結果的にそうなったということかもしれませんが、意識して分けたポイントはあります。
それは、リリースのタイミングと頻度です。
端的に言えば、大勢のユーザが利用しているサービス側と、数名の社員が使う業務機能側が同じサイクルで更新されるわけがないのです。 機能の改善や追加をするサイクルが違ってくるだろうという関心事が分割の基準になっています。
なぜ、リリース頻度を気にするかと言うと、コズレも言うに及ばず、アジャイル開発だからです。 スモールリリースをし、改善を回していくのが前提にあったので当初からサブシステムとして機能するアーキテクチャを設計したのでした。
マイクロサービスのデメリット
マイクロサービスのメリット・デメリットもぐぐれば、いろいろ出てくるのですが、ここではあるイベントでのLIFULLのCTO 長沢さんの言葉を引用させてもらいます。
分割を進めることを優先して、共通部分を無視してあえてマイクロサービス化した部分があるのですが、開発が進むと共通部分に関わるオーバーヘッドが段々無視できないレベルになってきてしまいました。ログの取得やインターフェースの部分です。効率的ではなくなってしまったので、現在は全体の共通基盤を作ろうとしているところです。
出典:後編:知ってるようで知らないマイクロサービス〜様々な意思決定のロジック~マイクロサービス化に必要な要件 | flexy(フレキシー)
ちなみにですが、コズレはLIFULLさんから投資していただいております。いつか、システム連携できるといいですね。
住み替えを検討する時って、子供が生まれた時なんですよね。住居の広さもそうですし、安全性や清潔さを保つ環境は、自分のためではなく子供のためを思ってこそなのです。 だから、弊社コズレとLIFULLさんは相性がよいんだと思っています。
コズレの共通基盤
もう一度、コズレのミニサービスの図をよく見てください。
気付きました?そうなんです。各 war や jar の中の構成はプレゼンテーション層(UI, API, PUBLISHER/SUBSCRIBER)以外は同じようになっているんです。 コズレでは、パッケージ内の構成をコーディング規約に明記し、独自のフレームワークでアプリケーション内の依存関係に制約をもたせるようにしています。
各3層構造が疎結合になるようにしているので、プレゼンテーション層の組み替えをするだけで、WEBアプリケーション用にも内部API用にも、バッチ処理系アプリケーション用にも、ビジネスロジック層が再利用できるんです。 とても美しいですね。
また、フレームワークは、各APIとのやり取り、MQでのやり取りをするための処理を共通化したり、上記引用での指摘があったようにログの仕組みなどをカバーしています。
また、コズレ独自のユーザセッションの仕組みがあるので、WEBサーバ上の各サブシステムは、ユーザ基盤でログイン後、そのユーザ認証情報を使えるようになっています。
名付けて、「cozre-common」というフレームワークを初期から使っています。
まとめ
コズレマガジンのアプリケーション構成について整理してみました。
個人的には、とても美しいアーキテクチャだとは思っていますが、現実にはリファクタリングしたい疎結合じゃないコードも存在しています。 時間があれば、リファクタリングしたいなぁと葛藤しています。テストケースを充実させたら、じゃんじゃんインターン生に直してもらうのもありかなぁと思う今日この頃です。
そんなコズレでは一緒に働いてくれるエンジニアを募集中です。 興味ある方は、ご連絡ください。
【Tech01】コズレを支える技術 システム構成編
はじめまして。コズレのエンジニアのもっちーと申します。
コズレは「子育ての喜びをもっと大きく!」を掲げてコズレマガジンという子育て世代のためのWEBメディアを運営しています。 現在では会員数60万人を超え、会員の属性とアンケートを活用したマーケティング事業にも力をいれています。
私はコズレの創業時から、サービス全般の設計、開発、保守、運用に携わり、他にもエンジニアチームの採用、教育、マネジメントも行ってきました。 もともとコズレにジョインする前はフリーランスでなんでも屋でした。 今もなんでも屋なCTOです。
システム構成の変遷について
コズレにはコズレマガジンとコズレコミュニティというサービスがあります。 コミュニティからマガジンに至るまでには紆余曲折があったのですが、それはまた別の機会に。
現在のメインサービスであるコズレマガジンは多い時で月間300万ユニークユーザ数のアクセスがあります。このサービスを支えるシステム構成の変遷について説明したいと思います。
コズレマガジン第1形態
システム構成図 その1
コズレマガジンは、最初、ワードプレスでスタートしました。 VPSサーバ1台の中に、ApacheとMySQLを立てて、運用していました。
この頃は、コンテンツ作成も手探りで、一般投稿された記事の他に、SEOの勉強をしながら社員も役員も記事を書いていました。 時々、バズる記事があり(「〇ン〇ンムキムキ体操」とか)、アクセス数が増えすぎるとサーバーが500エラーを返すような非力なシステム構成でした。
ユースケースを探る
当時、ワードプレスで作成したコンテンツはDB移行する想定で、次の新規システムの設計をはじめていました。
そして、先行して記事を作成し運用する中で貯めたSEO知見や、機能要件を次のシステムに活かしました。 そう言った意味では大事な布石となったシステムでした。
コズレマガジン第2形態
AWSを採用する
ワードプレスでコンテンツを増やしつつ、新しいシステムをフルスクラッチで設計し、当時は私と私が採用したもう一人のエンジニアの2名だけで開発をしていました。
クラウドを活用すれば、インフラエンジニアを雇うより安く、早くシステム構築できると説得し、AWSを利用することになりました。 なぜ、GCPでなく、AWSを選んだかと言うと、GCPのサポート体制の印象がよくなかったのが一番の理由です。
コズレにジョインする前に参画していたプロジェクトでは、GCPを使っていて管理画面とかには不満はありませんでした。 しかし、ある時発生したAPIエラーをGCP側はなかなか認めず、3ヶ月近くかけてログを集めて証拠固めをしてやっと非を認めるようなケースを目の当たりにしました。 ゴールドサポートにお金を払っているのに、これはひどいなと思った次第です。
もう一人のエンジニアもAWSがいいっすと言ったので、異議なしで決まりました。
システム構成図 その2
インフラとしてのシステム要件は、突発的な負荷上昇に耐えられること、システムがダウンしないようにすること。 いわゆる耐障害性と高可用性の2つです。
可用性を高める
まずは、高可用性のために冗長構成を基本としています。 ロードバランサを挟んでPROXYサーバとAPPサーバのセットが2台。EC2のインスタンスの中にDockerでコンテナ化しています。 その後ろに、データベース関連のミドルウェアがつらなります。
ちなみに、当時使っていたDockerのバージョンが1.7ぐらいだったと思います。開発環境やローカル環境でスクラップ&ビルドするに向いているとはいわれていましたが、本番環境に使っているケースは珍しいほうでした。
Dockerには、OSやミドルウェアの設定も含めてパッケージ化できて、ポータビリティーがあるので、障害からの復旧もしやすいし、増設しやすいメリットがあります。
私は昔、インフラの仕事をしていたときに、とある政令指定都市の役所のOSを含めたサーバ情報のバックアップをテーブに保存する仕組みの構築をしたことがあります。 システムはとても高価でしたし、バックアップ処理も時間がかかるし、不安定だし、大変だった思い出があります。
それに比べて、サーバごと(正確にはコンテナですが)簡単に保存できるDockerの仕組みが無料でできることに単純に凄いと思いました。
DBサーバのMySQLは、AWSのマネージドサービスRDSを採用し、マルチA-Zのアクティブスタンバイの構成。将来、予算がついたら、さらにマスタースレーブ構成にする予定でしたが、まずはデータのバックアップがしっかりできる事を重視しました。
AWSのRDSでは、スタンバイ側にデータが常に同期されており、バックアップもスタンバイ側から取得されるため、DBは無停止でバックアップができ、リストアもほぼ無停止で対応できます。
検索エンジンサーバのElasticSearchは、各EC2のインスタンス内に1つずつノードをつくってマスタースレーブ構成に。マスターが落ちるとスレーブが昇格する想定。実際に片方が落ちるケースが発生しましたが、サービスがダウンすることを回避できました。
インメモリDBサーバのRedisは、AWSのマネージドサービスElasticCacheを採用。 こちらもマスタースレーブ構成にしました。
各データベース系の役割は以下の通りです。
DBサーバ:トランザクション管理が必要な処理全般担当。編集中の記事データやユーザ情報など履歴も含めて管理。
検索エンジンサーバ:公開記事の参照担当。記事中のキーワード検索ができるだけでなく、インメモリなのでDBよりパフォーマンスが出るメリットがあり、コンテンツ表示速度にも効果的。
インメモリDBサーバ:ユーザのセッション情報管理や、パフォーマンスをあげるためのキャッシュ担当。
AWSならではの話ですが、冗長構成にした時にサーバを立てるアベイラビリティーゾーンを分散させることができます。東京リージョンだとap-northeast-1aとかap-northeast-1cとかが選べます。 某クラスメソッドさんとかのサイトを参考にお手本通りに構築したので、少し前に東京リージョンでのAWS障害が発生した時、コズレは大丈夫でした。 有名どころのソーシャルゲームとかがダウンして阿鼻叫喚なツイッターのタイムラインをおかずにしながら、その日はランチをしました。
障害に強くする
耐障害性としては、APPサーバをスケールアップとスケールアウトの2つの方法で対応できるようにしました。 スケールアップ(CPUやメモリなどのスペックアップ)についてはAWSを採用したことで簡単に変更できるので課題クリアです。 スケールアウト(サーバ台数の増設)については、DockerでPROXYサーバとAPPサーバのイメージを固めることで、EC2を増設しても、すぐに同じ構成が作れる状態を目指しました。
仮にですが、コズレマガジンがテレビで取り上げられてサーバ負荷がスパイクして急上昇した場合、一時的にサーバ台数を増やして対応できる事を想定していました。
これは、ECS(Elastic Container Service)というサービスが作られる前だったのでオートスケールを手動でやるイメージですね。
最終的な理想型は、負荷計測をもとに、負荷が高まったら4台になり、負荷が下がったら2台になるようなオートスケールです。 AWSではすでに実現できる手段を用意していたと思いますが、コズレではまだ実施していません。
コズレマガジン第3形態
ネイティブアプリをリリースする
コズレマガジンがWEBサービスとして無事リリースしてから半年後、ネイティブアプリの開発も始まりました。
システム構成図 その3
iOSやAndroidのアプリが通信するためのAPIサーバが必要になり、WEBサービス側との共通処理を内部APIに寄せることで、機能の1元管理をする改善をしていきました。
システム構成図では、WEBサービス側のAPPサーバからDB側への接続が消えてはいますが、省略しています。内部APIに寄せたものもあれば、直接DB側に接続している処理が残っているのもあります。
この頃、ECSなるDockerをデプロイできるサービスがAWSの東京リージョンでも使えるようになっていたので、内部APIとアプリ専用APIはECSで構築しています。
コズレマガジン第4形態
DBの負荷増加を軽減する
インフラ構成は変わらないまま、会員登録数が増え、日々配信しているメルマガ(コズナビといって、お子さんの月齢にあわせたコンテンツを届けています)や新サービスの口コミ機能とかDBへの問い合わせが増えるような処理が増えてきました。
コズレではDBサーバのCPU使用率などが規定値を超えるとSlackにアラート通知がくるようになっています。 瞬間風速的に100%いく時は致し方ないのですが、これが張り付くようになると問題です。 そんな自体が徐々に出始めていたので、DBサーバのスペックアップとマスタースレーブ構成への変更を実施しました。
システム構成図 その4
DBサーバのスレーブ作成はAWSのRDSがさくっと面倒をみてくれたので、楽でした。 自前でやるとレプレケーション用ユーザの設定やバイナリーログのポジションの同期化など手間なのですが、そこはマネージドサービスのいいところですね。
アプリケーションはマスタースレーブ構成に切り替える時がくるのを見越して、更新ユーザと参照ユーザをわけて処理をするような作りにしていたので、設定ファイルの接続先ホストをスレーブサーバへ変更するだけで切り替えできました。
5年越しの伏線回収です。グッジョブ、俺!と自画自賛しました。
その他のシステム構成について
主に、オンライン側のシステム構成の変遷について書いてきましたが、非同期処理やバッチ処理のためのサーバやログ収集をして分析するサーバも存在しているので、補足します。
システム構成図 その5
メール配信処理や画像のリサイズをしてアップロードし直す処理などオンラインで実施すると待ち時間が発生してしまうようなものはメッセージキューイングの仕組みを利用して非同期処理にしています。
コズレではRabbitMQというミドルウェアを利用しており、キューにたまったメッセージをJavaで作ったバッチジョブが受け取り、随時実行していきます。 メッセージを投げる側は、定時に始まるcronもあれば、オンラインからキックされるものも、バッチジョブが後続の処理をキックするものもあります。 上記の図の青い矢印線がメッセージの流れを表しています。
また、画像変換処理のようなものはメモリを食うので、オンラインのサーバで複数のユーザから同時に処理を受け付けるとメモリ不足が発生する可能性があります。 こういう時も、メッセージキューイングの仕組みを活用すれば、パラレル処理をシリアル処理にすることができ、1画像づつメモリを利用して処理していくので、障害にも強くなります。
システム構成図 その6
これまでの図では省略していましたが、各APPサーバにはログ転送をするサーバがセットになっていました。 これはシステム構成図2の頃から存在しています。
図の青い線がログの転送を意味しています。各転送元、転送先にいるのがFluentdです。このミドルウェアはプラグインが充実しており、カスタマイズしやすいのがいい点だと思います。 ちなみにエラーログは転送しないでSlack連携プラグインで通知するようにしています。
転送されたログはログ収集サーバに集められます。主なログはユーザの行動を記録するアクセスログやクリックログ、インプレッションログ等になります。 それらのうち、アクセスログはElasticSearchにインポートされ、Kibanaという分析ツールによって、リアルタイムに視覚化されています。
最終的にログはAWSのS3のストレージに保存され、現在はさらにCMPへのデータ統合をしています。
まとめ
コズレマガジンのシステム構成についての変遷を振り返ってみました。
今後はよりパフォーマンスをあげるためのシステム構成の改善を検討しています。
内部APIサーバはJavaで実装していますが、gRPC等にリプレイスするのもありだと思っています。 RabbitMQもApache Kafkaに置き換えたらバッチジョブの処理効率が上がるのではないかと検証も進めています。
そんなコズレでは一緒に働いてくれるエンジニアを募集中です。 興味ある方は、ご連絡ください。