様々なマンガアプリを素早く開発できる「GigaViewer for Apps」のしくみ バックエンド編

こんにちは、はてなで GigaViewer for Web とバックエンドを開発している id:cateiru です。 この記事は『Inside GigaViewer for Apps』連載の第4回目です。ここまではアプリを中心に連載していましたが、今回は Web とアプリの両方で使用しているバックエンドについて、複数サービスとアプリを支えるしくみを紹介していきます。

GigaViewer のバックエンド構成

GigaViewer では17社の25個1ものサービスを開発しています。この GigaViewer のバックエンドはモノレポのマルチテナントなシステムになっています。 さらに、アプリのデータもこのバックエンドへリクエストされるため GigaViewer で構成されているサービスは必ずここを通るというとても大事な部分となっています。

GigaViewer の概略構成図

Web版のサービスでは通常、このバックエンドから Varnish や nginx, CloudFrontを通じて皆さんのブラウザで表示されます。しかし、アプリ版では WebView で表示しているわけではないので GraphQL を API としてアプリと送受信をしています。

GigaViewer はマルチテナントなシステムであるため GraphQL との相性が非常に良いです。後述するサービスごとにある固有機能をアプリで選択でき、既存の機能の横展開ではバックエンド側で正しく展開可能に実装がされていればアプリ側で GraphQL のクエリを書き換えるだけで実装ができます。

マルチテナントにする理由とメリット課題

ではなぜ GigaViewer ではマルチテナントなシステムを採用しているのでしょうか、その理由について解説します。

アクセス数のピークはサービスによって分散する

皆さんはいつマンガを読みますか?通勤通学中やお昼、寝る前に読む方は多いと思います。しかし GigaViewer のリクエスト数のメトリクスを見ると、実はサービスの更新時にアクセス数が最も多くなる傾向を示しています。

あるサービスのリクエスト数のグラフ。突出している部分が更新日

そして、更新日はマンガサービスごとに異なります。そのため、高スペック2なサーバでマルチテナントを構築して待ち受けていれば急なスパイクアクセスでも難なくさばくことができ、さらに運用費用も抑えることができます。 このため、人気作品の最終回だったり、SNS起因でアクセス数が普段の数十倍になったりしても、読者の皆さんにマンガをお届けできるようになっています。

更新日によりピーク帯のスパイクを分散するイメージ図

ちなみに、少年ジャンプ+では週刊少年ジャンプの配信時刻である月曜日の 00:00 にスパイクが発生します。その対応手段などについて id:koudenpa さんが紹介しているのでぜひご覧ください。

機能を横展開しやすい

GigaViewer の機能は、基本的に横展開可能であることをベースに開発しています。マルチテナントにすることで、機能の横展開をする場合のバックエンドの開発工数を最小限に、機能提供が可能です。詳しい横展開ベースの開発については後述の「サービスごとにある固有機能と出し分け」で詳しく解説します。

システム障害が発生すると全サービスに影響しうるという課題

ここまでを見るとマルチテナントは便利!と思えますが、課題も存在します。 バグやオペレーションミスなどでシステム障害が発生し、アクセスができなくなるとすべてのサービスに影響してしまいます。 GigaViewer ではそのようなシステム障害を起こさないため開発やリリースでは、ブランチごとにプレビュー環境をデプロイできるしくみ3や自動で VRT を実行し差分を検出する CI、後述の Feature Flag を用いて段階的にリリースするなどを行っています。

サービスごとにある固有機能と出し分け

GigaViewer で提供されているサービスではそれぞれに固有機能が存在します。たとえば以下のような固有機能が存在します。

  • アカウント機能
    • メールアドレスとパスワードを使用してログインでき、ポイントの購入と読み物の購入ができる。
  • 単行本機能
    • エピソード以外に単行本を購入して読むことができる。
  • コメント機能
    • エピソードなどにコメントを投稿できる。
  • 完読機能
    • 対象の話を最後まで読み終わるとポイントがもらえる。

このような機能は固有に限らず、すべてのサービスに導入しても良いと思う方もいるかもしれません。
しかし、 GigaViewer を利用していただいている出版社ごとの運営方針だったり新機能のリリースで全サービスにリリースにはリスクがあり、カナリアリリースのように一部サービスから段階的に導入してきたいといったことがあるため GigaViewer では機能をサービスごとに 有効/無効 を切り替えることができる機能「Feature」が存在します。

この Feature はいわゆる Feature Flag です。機能のオン / オフをコード内にフラグとして持つことによってサービスごとで有効 / 無効を切り替えることができます。 GigaViewer ではこのフラグをサービスごとで設定できるようになっています。これにより、サービスごとにある固有機能の出し分けを実現しています。 たとえば先ほどの例の「アカウント機能」では UserAccount という Feature が用意されています。この Feature を有効にすることで、有効にしたサービスではほぼ追加実装なしにアカウントを作成できるようになっています4

GigaViewer では、新規機能を開発する際には Feature で出し分けできるようにして全サービスへ横展開が可能な実装を前提に行っています。

Feature を使用して機能を出し分けるさまざまな方法

さて、Feature という機能の説明をしましたが実際にどのようにしてこのフラグを使用して出し分けをする実装をしているのでしょうか。その方法についても解説していきます。

GigaViewer のバックエンドは Perl で実装されており、サービスの出し分けはHTTPのリクエスト時の情報からサービスを判別しています。
そして、HTTPリクエストに応じたサービスの情報が $media というインスタンス変数に格納されており、この変数を参照することでどのサービスのリクエストかを判断します。このインスタンス変数のメソッドに has_feature というものがあり、ここに出し分けをしたい Feature を指定することで Boolean で返るようになっています。内部では Scope::Container を使用して状態がキャッシュされるといった実装になっています。

if($media->has_feature('UserAccount')) {
  ...
}

また、ルーティング設定時などでも簡単に出し分けができます。以下のように設定することでその Feature を持っているサービスのみがそのエンドポイントを使用できるようになっています。

GET '/my' => '(handler)', { has_feature => ['UserAccount'] };

さらに、アプリでは GraphQL を使用しているためここでもルーティング設定時と同じように Feature によって使えるフィールドを制限できます。GraphQL のカスタムディレクティブを使うと独自にディレクティブを定義でき、GigaViewer ではこれを使用して @hasFeature というものを作成しています。

# Feature を指定できるカスタムディレクティブの例
getUser: UserAccount! @hasFeature(features: ["UserAccount"])

開発環境でのみ Feature を有効化する WIP Feature

GigaViewer では、開発環境で特定の Feature を有効化/無効化できる機能が存在します。この機能は WIP Feature と呼んでおり、WIP Feature フィールドに Feature を設定することでその Feature は「有効にするフラグ」が立っている場合にのみ有効になります。さらに、本番環境では必ず無効となります。 この機能を使用することで、開発中で本番ではまだ公開したくないけど開発環境では確認できるようにしたいという要望に対応しています。
大きい機能をリリースする際にも、事前に WIP Feature で開発し本番リリース時には Feature を WIP Feature フィールドから通常の Feature フィールドに移動するだけでリリースができるようになるので当日のリリースオペレーションがスムーズに行え、さらに問題が発生した際の切り戻しも簡単に行うことができます。

この機能はアプリでも使え、開発版のアプリでは専用のボタンが実装され、これをONにすることで WIP Feature を使えるようになっています。

また、この WIP Feature の有効化/無効化は社内専用のブラウザ拡張機能があり、これを使用することで開発者は特に考えることなく設定をすることが可能となっています。

終わりに

ここまで、GigaViewer のバックエンドについて簡単に説明してきましたがいかがだったでしょうか?GigaViewer のバックエンドは Web だけでなくアプリからのアクセスをさばくため巨大で複雑な実装になっていますが、そのぶん非常に多くの知恵と工夫があります。 連載企画『Inside GigaViewer for Apps』では、このような GigaViewer に関するしくみなどをアプリ側の視点を中心にお届けしていますのでほかの記事もぜひチェックしてみてください!


  1. 2025年3月現在(GigaViewer - 株式会社はてな
  2. GigaViewer は ECS で動いており高負荷時にはスケールアウトをするが、データベースは1つで待ち受けるためかなり大きなインスタンスで運用されています。
  3. 既存のDeploy Preview環境をmirage-ecsに移行する - 設計編 - Hatena Developer Blog
  4. 実際に機能を横展開する場合は外部の決済サービス導入やサービス個別のスタイル調整などただ Feature を有効化するだけでは使えないことが多くあります。