モジュラーモノリスへ ~過剰なマイクロサービスの統廃合に取り組み始めた話~
こんにちは。
FiNC Technologies でサーバーエンジニアをしている沼尻です。
開発ブログの更新が久々になってしまいました。弊社は一時期と比べると開発体制は縮小しながらも、多くの企業との協業等も引き続き進めており、各方面でチャレンジの機会をいただいています。
2023年冬には、弊社の主要サービスである「FiNC」アプリが大幅にリニューアルされました!「生活動線にあるアプリ」として、歩いてガチャを回しマイルを貯めるという体験を中心に、健康管理を楽しく続けられるアプリに生まれ変わっています。
詳細はぜひアプリを触ったり、記事をご覧ください。今回はリニューアルに伴い開発サイドで起きた話を紹介したいと思います。
開発の歴史
弊社のサービスローンチ期である 2016 年前後は「マイクロサービスアーキテクチャ」がトレンド化しており、海外では Netflix が大きな成果をあげたことで有名ですが、国内でもメルカリ等 多くの採用事例が続き話題となっていきました。弊社もマイクロサービスを採用し、当時においては比較的先駆者として様々なプラクティスを導入してきました。
開発チームの拡大に伴い、それぞれ開発のしやすさやデプロイ速度を優先し機能や案件に応じて サブスクリプション機能を管理するサービス、チャット機能を管理するサービス、健康プログラムを提供するサービスといったようにサービスはどんどん増えていきます。
気づけば、大小あれどサービス数は 60 以上に..
結果としてそれに詳しい人が不在になる多くの「無人化サービス」が発生することになりました。
いわゆるマイクロサービスのメリットとして挙げられる「開発規模を小さくできる」や「障害の分離」「技術採用の自由度」は一定享受できた反面、近年は組織規模に対して増えすぎたサービスのメンテナンスコストが無視できない状態となっていました。2023年から3代目 CEO / CTO 体制になったこともあり、根本的な課題に向き合う風潮が強まっていきました。
「FiNCが直面した」マイクロサービスのデメリット
【運用面】
- ライブラリ管理、アップデートコスト
- そもそもどの機能に何が使われているのか把握するのが困難
-アップデートの抜け漏れ
- 直近実施した ruby update においては対象のサービスが全てリリースされるまで約 8 ヶ月かかった..(実施決定 -> 割り振り -> 各チームスプリントの合間に対応 -> テスト -> 修正 ->リリース) - 監視コスト
- アラートチャンネルのメンバー管理も含む - 各サービスに散らばる管理画面
- サービスを跨いだ認可の仕組み作りが非常に難解
- CS や運用担当者はいくつもの管理画面を駆使する必要がある - データの一貫性を維持しにくくなる
- 一貫性や整合性担保のための適切な知識を有することは重要だが、どうしても設計や実装コストは上がってしまう
【学習面】
- ドキュメントがあったりなかったり、形式が揃っていなかったり
- 環境構築するのも一苦労。4, 5 サービス立ててはじめて動かせる機能も
- 各チーム局所最適でマイクロサービス化を推進したことで、サブスク有料会員や法人会員(法人向けサービスである FiNC for BUSINESS の一部ご利用者も FiNC アプリを使用している)の管理等が分割されている - サービスを跨いだ共通の決まり事がなく、チーム単位で新しい技術を各サービスに導入。推進派が退職等で不在になると、当該技術の採用の目的や背景が不透明なまま、言語やフレームワークが散らばった状態に
【インフラ・保守面】
- 監視の分散、ダッシュボードの複雑化
- コスト負担
- Rails というメモリを食いがちなプロセスに加え、コンテナを大量に建てる必要がある -> 必要とされるサーバスペックが大きく、コスト増となる
サービス分散の苦い経験を踏まえ、社内の開発組織・チーム形態に合わせて、モノリスとは言わずとも程よい数・規模のマイクロサービスへの集約を目指すことになり、今回のアプリリニューアルを機に必要機能を既存の 1 サービス内に構築していく「モジュラーモノリス」の選択肢を考えるようになりました。
モジュラーモノリスとは
マイクロサービスのように複数のサービスを別アプリケーションとして完全に分離するのではなく、アプリケーションの内部を「モジュール」という単位で分割したアーキテクチャスタイルを指します。内部的な分割にとどまるため各システムの独立性をある程度保ちつつ、モノリスのように1つのデプロイパイプラインを維持する特徴があります。
分割の実現方法
弊社では Ruby 製のアプリケーションが多く存在しますが、同一アプリケーション内にあるモジュール間での呼び出しをどのように制御するかが一番の鍵となります。モジュール間で意図しない、または無作為な呼び出しが許容されるとさらにアーキテクチャが破綻していくのは明らかです。
そこで今回は Shopify 社が提供している packwerk という gem を利用することにしました。packwerk はパッケージという単位のファイルグループを作ることができ、パッケージ間の呼び出しを静的解析でチェックします。
チェック方法は `bundle exec packwerk check` コマンドを実行するだけで、CI に組み込むこともできます。 検査の結果違反があった場合、下記のいずれかのアクションを取ることになります。
- package.yml に依存関係の設定を追記する
- 違反のあるコードを解消する
- deprecated_references.yml に未解決事項として記録する
packwerk 採用の背景
今回「脱マイクロサービスPJ」が社内で立ち上がったわけではなく、あくまでアプリのリニューアルを機に消せるものは消そう、無作為に増やすのはやめようという背景があったため、最優先すべきことはあくまで「リニューアル開発」でした。そしてリリース日は関係取引先とも握っているため、開発納期は変更不可なものとしてこれらの活動を進める必要がありました。
その中で、採用を決めたポイントとして以下の点が挙げられます。
- 導入事例やデモが割と充実しており、試しに触った際にさほど迷わずやりたいことが構築できた
- 公式ドキュメント に加え下記の導入事例等 参考にさせていただきました
◉ モノリシックRailsアプリケーションを モジュラモノリスへ移行している noteの事例
◉ モノリスなRailsにモジュラーモノリスを導入した話 — hacomono TECH BLOG
◉ タイミーはどのようにしてモジュラモノリス化を進めたか - 依存関係の可視化が可能
- モジュールの独立を段階的に実現していくことができる(前述の通り未解決事項を記録しながら進められる)
- 独立サービスとして切り出す方が適切とされた場合、実装構造的に切り出しが容易である
導入してみた感想
結果的に FiNC アプリのリニューアルで新たに「マイル」「景品」「ガチャ」「ランク」等の機能を実装する必要がありましたが、それらは全て既存アプリケーションの 1 モジュールとして実装されました。
1 からインフラ整備がいらないので、開発初期からそれなりに開発速度を出せた中で、取り組みを進めていたリニューアルチームが遭遇した問題や課題を以下に書きます。
- 特殊なディレクトリ構造になるため初見の人には驚かれる(特に※部分。社内周知やドキュメントが必要)
package.yml
app/ # 非packageコード
models/
...
packages/ # packageコード
my_domain/
README.md
package.yml
deprecated_references.yml
app/
public/ # public IF for 同リポジトリサービス
my_domain/
# ※ packages直下のmy_domainはあくまでpackage分割ディレクトリとして機能しているので
# MyDomain::XXXというnamespaceにするには、さらにmy_domainと切る必要がある。
# またpublic classであることを明示するため、導入事例を参考に弊社ではさらに「public」というnamespaceを切る運用にしている。
xxx.rb
services/ # privateサービス
my_domain/
private_service.rb
models/ # privateモデル
my_domain/
private_model.rb
controllers/ # API for 非同リポジトリサービス
...
spec/
...
my_other_domain/
... # 構造は同じ
- packwerk の README に記載がある通り、いくつかの違反検出が漏れる可能性がある
- 引数で別パッケージのオブジェクトを受け取っていた場合等
- パッケージの css を変更しても cdn 経由で取得できる css ファイルが入れ替わらない
- asset version の検出に含まれていなかったことが原因だった - パッケージの spec が CI で実行されてなかった
- 各自手元で該当 spec を実行後 PR 提出や開発環境へのデプロイを行なっていたものの、パッケージの spec が CI 上実行(指定)が漏れていたことに途中まで気づかなかった.. - CI 実行時間が長くなる
- 1 サービスに複数機能が包括されコード量が増える分、特に spec の実行時間が数十分と長くなる問題があった。そこで、master 以外のブランチでは変更のあったパッケージの spec のみを実行するようにして時間短縮を図った。
これらいくつかの問題や課題に遭遇しましたが(アプリケーションの設定や作りによるものもあると思います)、導入して約半年が経過し概ね期待通りの役割を果たしてくれていると感じます。
まとめ
今回アプリのリニューアルを機に、マイクロサービスに固執せずモジュラーモノリスでのサービス運用を開始した事例を紹介しました。今後もしばらくは増えすぎたサービスの統廃合を平行して進めていく予定です。
サービスを増やすことは必ずしも悪いことではありませんが、適切な設計(これ自体の難易度もかなり高い)と運用組織体制があり、それらが中長期的に継続されることの上に成り立ち得るアーキテクチャであることを数年かけて身をもって体験しました。今後も外部環境も含め、事業・プロダクトの進化に合わせて適宜リアーキテクトを行っていきたいと思います。
我々は “Design your well-being” を掲げ、若手のリーダーシップと突破力、経験豊富なシニアの知見の両方を武器に、人々のwell-being を支えるプロダクト開発を推進しています。採用も進めておりますので、まずはカジュアル面談からでもお気軽にご連絡ください。