【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に置き換えたらバッチジョブの処理効率が上がるのではないかと検証も進めています。
そんなコズレでは一緒に働いてくれるエンジニアを募集中です。 興味ある方は、ご連絡ください。