BacklogのGitホスティングにおける

冗長化と負荷分散の仕組み

NuCon mini '22 Spring in ONLINE

Yuichi Watanabe at Nulab Inc.

Profile

me := Profile{
  Name:    "Yuichi Watanabe",
  Org:     "Nulab Inc.",
  Product: "Backlog",
  Lang:    "Go",
  Twitter: "https://twitter.com/vvvatanabe",
  GitHub:  "https://github.com/vvatanabe",
  Slides:  "https://slides.com/vvatanabe",
  Lives:   "Fukuoka",
}
  • 取り扱うデータの特性

  • システム設計上の留意事項

  • アーキテクチャの外観

  • レプリケーションの仕組み

  • 複製と分散の中核となるリバースプロキシ

  • まとめ

Agenda

  • Gitホスティングではベアリポジトリと呼ばれるリポジトリを取り扱う
  • ベアリポジトリとはワークツリーをもたず.git/内のファイルのみを持つ
    • objects (Tree, Blob, Commit, Tag等のGitオブジェクト)
    • refs (TagやBranch等の参照)

  • 所謂オブジェクトデータベース

リポジトリという単位のオブジェクトデータベース

# 取り扱うデータの特性

├── branches
├── config
├── description
├── HEAD
├── hooks
├── index
├── info
├── objects
│   ├── 01
│   │   └── a4798bf8e9872b0ae0301657d9908a0d8bd789
│   ├── cb
│   │   └── 311c3d61afbe9f979770be8f999b66e7550ec8
│   ├── d1
│   │   └── 85f2630d528949f038e055d6b35d5954a8a0d5
│   ├── info
│   └── pack
└── refs
    ├── heads
    │   └── master
    └── tags
        └── v1.0.0
  • 取り扱うデータの特性

  • システム設計上の留意事項

  • アーキテクチャの外観

  • レプリケーションの仕組み

  • 複製と分散の中核となるリバースプロキシ

  • まとめ

Agenda

NFS、Amazon EFSのようなファイルストレージ

ストレージとパフォーマンス

# システム設計上の留意事項

  • NFSのようなファイルストレージは複数のサーバーからマウントできる

  • フルマネージドなAmazon EFSなら耐障害性も高く非常に使いやすい

  • しかしリポジトリの状態によってCPUやディスクIOが跳ね上がることも多々ある

  • ブロックストレージと比較すると10倍程度遅くなる場合も

  • Gitホスティングでは安定したパフォーマンスを保ったまま使用するのは難しい

Amazon S3のような外部のオブジェクトストレージ

ストレージとパフォーマンス

  • s3fs(FUSE)等のファイルシステムとしてマウントさせるツールが必要

  • Gitオブジェクトや参照は一度に大量に送受信されることも多々ある

  • 読み書きするファイルの量と比例して線形に通信コストも増える

  • ファイルストレージと同じくパフォーマンス面がボトルネックとなる

# システム設計上の留意事項

Amazon EBSのようなブロックストレージ

ストレージとパフォーマンス

  • 安定したパフォーマンスを考慮するとブロックストレージが最適

  • しかし一般的に複数のホストから同じ領域へ安全に書き込みできない

  • 複数のホストから安全に取り使うために物理的に異なるストレージへ複製が必要

# システム設計上の留意事項

ストレージ間のデータの複製と整合性

  • Git自体にMySQLやPostgreSQLといったRDBが持つレプリケーション機能はない
  • 物理的に異なるストレージ間でリアルタイムにデータを複製する安全な方法を考える必要がある
  • 複製時に予期しないエラーが発生した時など、Gitリポジトリとしての整合性を保つ必要がある

# システム設計上の留意事項

状態に応じた動的なプロキシ

  • リポジトリを一定の集合で異なるストレージへ分割して負荷を分散させる
  • リクエスト毎に対象リポジトリを持つサーバーへ動的に振り分ける仕組みが必要

# システム設計上の留意事項

  • 取り扱うデータの特性

  • システム設計上の留意事項

  • アーキテクチャの外観

  • レプリケーションの仕組み

  • 複製と分散の中核となるリバースプロキシ

  • まとめ

Agenda

  • Backlogのシステム全体からGitホスティング機能を中心に切り出したアーキテクチャの外観の図

  • 図を簡素化するために、AZ、VPC、サブネット、ロードバランサーなどは省略

  • 実際は各コンポーネントをMulti AZに配備している

# アーキテクチャの外観

アーキテクチャの概略図

  • BacklogのGitホスティングのリクエストは大きく4つに分類される

  • 処理を担当するコンポーネントも分割されている

    • Webブラウザからのリクエスト

    • Backlog APIからのリクエスト

    • GitクライアントからのHTTPSリクエスト

    • GitクライアントからのSSHリクエスト

  • システムの前段に配備されるのコンポーネントはストレージを持たない(ステートレス)

  • リクエストの特性に合わせた認証・認可を行う

  • 後段のコンポーネントにRPCで接続してデータの読み書きを行う

ステートレスなフロントエンド

# アーキテクチャの外観

ステートフルなバックエンド

# アーキテクチャの外観

  • システムの後段に配備されるコンポーネントのgit-rpcのみストレージを持つ(ステートフル

  • フロントエンドからのRPCを受け取り実際にGitリポジトリへ読み書きを行う

  • Gitリポジトリの読み書きに特化したデータベースミドルウェアのようなもの

  • EC2で稼働しておりEBSをマウントしている

  • Active/ActiveなPrimary/Replica構成

  • ReplicaはPrimaryを正とした複製

  • Write系のRPCはPrimaryで処理する

  • Read系のRPCはPrimaryとReplicaで処理する

フロントエンドとバックエンドを繋ぐプロキシ

# アーキテクチャの外観

  • 中央に配備されているgit-proxyはフロントエンドからの全てのRPCを受け取りバックエンドへ中継する

  • レプリケーションにおいて中心となるコンポーネント ※ 詳細は後述

  • 図の全てのコンポーネントは全てgRPCで通信する

  • gRPCは4種類の通信方式をサポートしている

    • Server Streaming RPC

    • Client Streaming RPC

    • Bidirectional Streaming RPC

    • Unary RPC

  • これらはGit特有のワークロードを効率化するのに非常に適している

gRPCを用いたサービス間通信

# アーキテクチャの外観

gRPCを用いたサービス間通信

# アーキテクチャの外観

大容量のデータ読み込み

例)git clone、git pull、git fetch、 ファイルのダウンロード

  • サーバーが一度に送信するデータを抑えたい

    • 複数のレスポンスに分割したい

      • gRPCのServer Streaming RPCが適している

gRPCを用いたサービス間通信

# アーキテクチャの外観

大容量のデータの書き込み

例)git push

  • サーバーが一度に受信するデータを抑えたい

    • 複数のリクエストに分割したい

      • gRPCのClient Streaming RPCが適している

gRPCを用いたサービス間通信

# アーキテクチャの外観

その他小さなデータのやり取り

例)コミット、ブランチ、タグ等の比較的小さなデータの取得

  • リクエストやレスポンスを分割する必要はない

    • gRPCのUnary RPCが適している

  • 取り扱うデータの特性

  • システム設計上の留意事項

  • アーキテクチャの外観

  • レプリケーションの仕組み

  • 複製と分散の中核となるリバースプロキシ

  • まとめ

Agenda

  • どのようにリポジトリを複製しているのか?

    • 俗に言う非同期レプリケーション方式

  • git-proxyは書き込み処理を中継する直前にレプリケーションログ(※1)をAmzon S3へ保存する

  • ログを保存した直後や変更した直後に別のアプリケーション(※2)からそのデータにアクセスしても常に最新の状態を取得できる必要がある

  • Amazon S3はStrong Consistency(強い一貫性)をサポートしており、この要件を満たしているので、一連のレプリケーションプロセスを円滑に実施できる

Amazon S3に保持するレプリケーションログ

# レプリケーションの仕組み

※1 リポジトリを複製するために必要な情報を記述したログ

※2 git-replication-workerや冗長化した複数のgit-proxyから参照される

  • git-proxyは書き込み処理を中継した後、処理が成功した場合、対象のレプリケーションログのユニークなキーを含んだメッセージをAmazon SQSに送信する
  • 書き込み処理が失敗した場合は不要になったAmazon S3上のレプリケーションログを削除する

Amazon S3に保持するレプリケーションログ

# レプリケーションの仕組み

  • レプリケーションログはリポジトリ単位で発行される

  • ログの種類は書き込みの特性に応じて複数のイベントに分類される

  • レプリケーションの実行は例のように実行順序を守る必要がある

    例)

    1. リポジトリの作成

    2. リポジトリの書き込み

    3. リポジトリのリネーム

    4. リポジトリの削除

Amazon SQSのFIFO・グループ化を用いた配信順序の担保

# レプリケーションの仕組み

  • Amazon SQSのFIFOキューがもつメッセージのグループ化機能が非常に有用
  • エンキューするメッセージにグループIDを付与することで同一グループIDのメッセージの配信順序が保証される

Amazon SQSのFIFO・グループ化を用いた配信順序の担保

# レプリケーションの仕組み

  • git-replication-workerはコンシューマ(ワーカー)としてAmazon SQSをポーリングしている

  • AWS SDK for Goをベースにして実装しており、goroutineとchannelを使い同時処理数を制御しています。

  • 単独のECSタスクで並列にメッセージを捌く

    • git-replication-worker自体も冗長化している

  • 取得したメッセージに含まれるレプリケーションログのキーを元にAmazon S3上のレプリケーションログの実態を取得する

コンシューマーによるレプリケーションの制御

# レプリケーションの仕組み

  • レプリケーションログはリポジトリの情報やレプリケーションの種別を持つ

  • ログの情報をもとにgit-rpcのレプリカに対してレプリケーション用の適切なRPCを実行する

  • レプリケーション用のRPCが成功した場合

    • レプリケーションログを消化

    • Amazon S3とAmazon SQSからログとメッセージを削除

  • 失敗した場合

    • リトライ

コンシューマーによるレプリケーションの制御

# レプリケーションの仕組み

  • git-rpcが提供するレプリケーション用のRPCはレプリケーションの種別毎に提供している

  • それらは全てリトライされることを想定しており、何度も実行されても問題ないように冪等な作りになっている

    例)Gitオブジェクトや参照の複製

    • レプリカからプライマリにGitのサブコマンドのgit fetchを実行

    • git fetchのトランザクションにより予期しないエラー発生時もデータの一貫性を保つ

    • git fetch自体が冪等なので複数回実行しても差分のみ処理できる

冪等性を担保したレプリケーションRPC

# レプリケーションの仕組み

レプリケーションのシーケンス

# レプリケーションの仕組み

レプリケーションのシーケンス

# レプリケーションの仕組み

レプリケーションのシーケンス

# レプリケーションの仕組み

レプリケーションのシーケンス

# レプリケーションの仕組み

レプリケーションのシーケンス

# レプリケーションの仕組み

レプリケーションのシーケンス

# レプリケーションの仕組み

レプリケーションのシーケンス

# レプリケーションの仕組み

レプリケーションのシーケンス

# レプリケーションの仕組み

  • 取り扱うデータの特性

  • システム設計上の留意事項

  • アーキテクチャの外観

  • レプリケーションの仕組み

  • 複製と分散の中核となるリバースプロキシ

  • まとめ

Agenda

  • git-proxyはL7のリバースプロキシ

  • gRPCのリクエストの属性やリポジトリの状態にあわせて動的に分散させる

    • 対象リポジトリの複製が完了しているか判定

    • リクエストの属性の判定

    • 特定のリポジトリグループごとに振り分け先のクラスタ(※1)を指定するカスタムルーティング

  • 書き込みの種類によって適切なレプリケーションログを発行する​

    • Amazon S3へログの送信

    • Amazon SQSへメッセージを通知

動的なリバースプロキシの制御

# 複製と分散の中核となるリバースプロキシ

※1 ストレージを持つPrimaryとReplicaの集合

  • 複製と分散の機能が一つのアプリケーションに集約されているためgit-proxyそのものをシステムから着脱可能

  • ローカルマシンで開発する場合や、エンタープライズ版でスタンドアロンなサーバーにインストールする場合など、冗長化機能が不要なケースを考慮

場面に合わせて着脱可能なコンポーネント

# 複製と分散の中核となるリバースプロキシ

  • 取り扱うデータの特性

  • システム設計上の留意事項

  • アーキテクチャの外観

  • レプリケーションの仕組み

  • 複製と分散の中核となるリバースプロキシ

  • まとめ

Agenda

  • リポジトリという名のオブジェクトデータベースをブロックストレージに保持している

  • リクエストの特性に応じて処理するコンポーネントを分割している

  • ストレージを持つコンポーネントはバックエンドに集約、Active/ActiveなPrimary/Replica構成で冗長化している

  • 全てのコンポーネントはgRPCで繋がり、リクエストの特性に応じて通信方式を選択している

  • 中核となる動的なリバースプロキシが複製と分散の舵を取っている

  • PrimaryとReplicaをリポジトリ単位で非同期にレプリケーションしている

Gitホスティングでは

# まとめ

  • 今後のワークロードの変化によっては、非同期なレプリケーションから同期的なレプリケーションへ手法を変更する可能性もある

  • 所謂3フェーズコミットと呼ばれる分散アルゴリズムを用いて、複数のノードへ同時に書き込みを行う手法などが挙げられる

  • これからも、プロダクトの成長と共にプロダクトを支える技術も健全に成長させていきたい

今後の展望

# まとめ

NuCon> git commit -m "ご静聴ありがとうございましたʕ◔ϖ◔ʔ"

BacklogのGitホスティングにおける冗長化と負荷分散の仕組み

By Yuichi Watanabe

BacklogのGitホスティングにおける冗長化と負荷分散の仕組み

  • 941