データベースの負荷分散

GitLab Premium9.0で導入されました

読み取り専用クエリを複数のデータベースサーバー間でディストリビューションします。

概要

データベースのロードバランシングは、複数のコンピューティングリソースにわたるデータベースのワークロードのディストリビューションを改善します。 ロードバランシングは、リソースの使用を最適化し、スループットを最大化し、応答時間を最小化し、単一のリソースの過負荷を回避することを目的としています。 単一コンポーネントの代わりにロードバランシングで複数のコンポーネントを使用すると、冗長性によって信頼性と可用性が向上する可能性があります。ウィキペディアの記事

GitLabでデータベースのロードバランシングが有効になっている場合、Redisのような外部依存関係なしに、シンプルなラウンドロビンアルゴリズムを使ってロードバランシングが行われます。 ロードバランシングはSidekiqでは有効になっていません。これは一貫性の問題につながるためで、Sidekiqはいずれにせよほとんど書き込みを行います。

次の画像では、すべてのセカンダリ(db4,db5,db6)に負荷が均等に分散されていることがわかります。SELECT のクエリは(必要な場合を除き)プライマリに送信されないため、プライマリ(db3)にはほとんど負荷がかかりません。

DB load balancing graph

要件

ロードバランシングを行うには、少なくともPostgreSQL 11以降が必要です、MySQLはサポートされていません。また、少なくとも1つのセカンダリをホットスタンバイモードにしておく必要があります。

また、ロードバランシングでは、データベースのフェイルオーバー後も、設定したホストは常にプライマリを指す必要があります。 さらに、ロードバランシングのために追加するホストは、常にセカンダリデータベースを指す必要があります。 つまり、すべてのデータベースの前にロードバランスを置き、GitLabがそれらのロードバランサに接続するようにします。

例えば、プライマリ(db1.gitlab.com)と2つのセカンダリ(db2.gitlab.comdb3.gitlab.com)があるとします。 このセットアップでは3つのロードバランサが必要で、各ホストに1つずつ必要です。 例えば、次のようになります:

  • primary.gitlab.com に対してdb1.gitlab.com
  • secondary1.gitlab.com に対してdb2.gitlab.com
  • secondary2.gitlab.com に対してdb3.gitlab.com

ここで、フェイルオーバーが発生し、db2が新しいプライマリになったとします。 つまり、フォワーディングは以下のように行われるはずです:

  • primary.gitlab.com に対してdb2.gitlab.com
  • secondary1.gitlab.com に対してdb1.gitlab.com
  • secondary2.gitlab.com に対してdb3.gitlab.com

GitLabではこのようなことは行ってくれませんので、自分で行う必要があります。

最後に、ロードバランシングを行うには、ロードバランシングを有効にするセクションで設定したのと同じ認証情報とポートを使ってGitLabがすべてのホストに接続できる必要があります。 ホストごとに異なるポートや認証情報を使うことはサポートされていません。

ユースケース

  • 数千人のユーザーと高いトラフィックを持つGitLabインスタンスでは、データベースのロードバランシングを使用してプライマリデータベースの負荷を軽減し、応答性を高めることで、GitLab内のページロードを高速化することができます。

ロードバランシングの有効化

ロードバランシングを使用したい環境では、以下を追加する必要があります。 これにより、host1.example.comhost2.example.comの間で負荷がバランスされます。

オムニバスのインストールで:

  1. /etc/gitlab/gitlab.rb を編集し、以下の行を追加します:

    gitlab_rails['db_load_balancing'] = { 'hosts' => ['host1.example.com', 'host2.example.com'] }
    
  2. ファイルを保存し、変更を有効にするために GitLab を再設定します。


ソースからのインストールで:

  1. /home/git/gitlab/config/database.yml を編集し、以下の行を追加または修正してください:

    production:
      username: gitlab
      database: gitlab
      encoding: unicode
      load_balancing:
        hosts:
          - host1.example.com
          - host2.example.com
    
  2. ファイルを保存し、GitLabを再起動して変更を有効にします。

サービスディスカバリー

GitLab Premium11.0から導入されました

サービスディスカバリーを使うと、database.yml の設定ファイルでセカンダリデータベースを手動で指定する代わりに、GitLab が自動的にセカンダリデータベースのリストを取得できるようになります。 サービスディスカバリーは、DNS の A レコードを定期的にチェックし、このレコードが返す IP をセカンダリのアドレスとして使うことで動作します。 サービスディスカバリーが動作するために必要なのは、DNS サーバーと、セカンダリの IP アドレスを含む A レコードだけです。

サービス・ディスカバリーを使用するには、database.yml の設定ファイルを以下のように変更する必要があります:

production:
  username: gitlab
  database: gitlab
  encoding: unicode
  load_balancing:
    discover:
      nameserver: localhost
      record: secondary.postgresql.service.consul
      record_type: A
      port: 8600
      interval: 60
      disconnect_timeout: 120

ここでは、discover: セクションで、サービス発見に使用する設定の詳細を指定します。

設定

以下のオプションを設定できます:

オプション 説明 デフォルト
nameserver DNS レコードの検索に使用するネームサーバー。 ローカルホスト
record 検索するレコード。 このオプションは、サービスディスカバリーが機能するために必要です。  
record_type 検索するレコードタイプはAかSRVのどちらかです(GitLab 12.3以降) A
port ネームサーバーのポート。 8600
interval DNSレコードをチェックする最短時間(秒)。 60
disconnect_timeout ホストリストが更新された後、古い接続がクローズされるまでの時間(秒)。 120
use_tcp UDPの代わりにTCPを使用したDNSリソースの検索 false

record_typeSRVに設定されている場合、GitLab は引き続きラウンドロビンアルゴリズムを使用し、レコード内のweightpriority は無視します。SRV レコードは通常 IP の代わりにホスト名を返すので、GitLab は SRV レスポンスの追加セクションで返されたホスト名の IP を探します。ホスト名の IP が見つからない場合、GitLab は設定されたnameserver に問い合わせ、A または AAAA レコードを探し、IP を解決できない場合は最終的にこのホスト名をローテーションから外します。

interval 値は、チェック間の_最小_時間を指定します。AレコードのTTLがこの値より大きい場合、サービスディスカバリーはそのTTLを尊重します。 たとえば、AレコードのTTLが90秒の場合、サービスディスカバリーはAレコードを再度チェックする前に少なくとも90秒待ちます。

ホストのリストが更新されると、古い接続が終了するまでに時間がかかることがあります。disconnect_timeout 設定を使用すると、古いデータベース接続をすべて終了するまでにかかる時間に上限を設けることができます。

(Consulのような) いくつかのネームサーバは、UDPで問い合わせをしたときに、 ホストの切り捨てられたリストを返すことがあります。 このイシューを克服するために、use_tcptrueに設定することで、問い合わせにTCPを使うことができます。

フォーク

注:GitLab 13.0から、PumaはGitLabオールインワンパッケージベースのインストールやGitLab Helmチャートデプロイで使用されるデフォルトのウェブサーバです。

Unicornのようにフォークするアプリケーションサーバーを使用している場合は、フォーク_後に_サービス検出を開始するようにUnicornの設定を更新する_必要が_あります。 これを行わないと、親プロセスでのみサービス検出が実行されるようになります。 Unicornを使用している場合は、Unicornの設定ファイルに以下を追加します:

after_fork do |server, worker|
  defined?(Gitlab::Database::LoadBalancing) &&
    Gitlab::Database::LoadBalancing.start_service_discovery
end

これにより、親プロセスとすべての子プロセスの両方でサービス検出が開始されるようになります。

クエリのバランス

読み取り専用のSELECT クエリはすべてのセカンダリホスト間でバランスされます。それ以外のすべて(トランザクションを含む)はプライマリで実行されます。SELECT ... FOR UPDATE のようなクエリもプライマリで実行されます。

ステートメント

プリペアド・ステートメントはロード・バランシングとは相性が悪く、ロード・バランシングが有効になると自動的に無効になります。 これはレスポンスのタイミングには影響しないはずです。

プライマリー・スティッキング

書き込みが実行された後、GitLabは書き込みを実行したユーザーにスコープされた一定時間プライマリの使用に固執します。 GitLabはセカンダリの使用に追いつくか、30秒後にセカンダリの使用に戻ります。

フェイルオーバー処理

フェイルオーバーやデータベースが応答しないイベントの場合、 ロードバランサは次に利用可能なホストを使おうとします。 利用可能なセカンダリがない場合は、代わりにプライマリでオペレーションが 行なわれます。

データ書き込み時に接続エラーが発生した場合、オペレーションは指数関数的なバックオフを使用して最大3回まで再試行されます。

ロードバランシングを使用する場合、データベースサーバーを再起動しても、すぐにユーザーにエラーが表示されることなく、安全に再起動できるようにする必要があります。

ロギング

ロードバランサはdatabase_load_balancing.logに様々なイベントを記録します。

  • ホストがオフラインとマークされた場合
  • ホストがオンラインに戻ったとき
  • すべてのセカンダリーがオフラインの場合
  • クエリの衝突により、別のホストで読み取りが再試行された場合

ログは、各エントリが少なくともコンテナを含むJSONオブジェクトで構成されています:

  • フィルタリングに便利なevent フィールド。
  • 人間が読めるmessage フィールド。
  • イベント固有のメタデータ、db_host
  • 常にログに記録されるコンテキスト情報。例えば、severitytime

使用例:

{"severity":"INFO","time":"2019-09-02T12:12:01.728Z","correlation_id":"abcdefg","event":"host_online","message":"Host came back online","db_host":"111.222.333.444","db_port":null,"tag":"rails.database_load_balancing","environment":"production","hostname":"web-example-1","fqdn":"gitlab.example.com","path":null,"params":null}

陳腐化した読書への対応

GitLab Premium10.3で導入されました

古いセカンダリからの読み込みを防ぐために、ロードバランサはセカンダリがプライマリと同期しているかどうかをチェックします。 データが十分に新しいと判断された場合、そのセカンダリを使うことができ、そうでない場合は無視されます。 これらのチェックのオーバーヘッドを減らすために、一定の間隔でのみチェックを行います。

この動作に影響を与える3つの設定オプションがあります:

オプション 説明 デフォルト
max_replication_difference セカンダリがしばらくの間データをレプリケートしていない場合に、セカンダリが遅れをとることを許されるデータ量(バイト単位)。 8 MB
max_replication_lag_time セカンダリの使用を停止するまでの、セカンダリの遅れを許容する最大秒数。 60秒
replica_check_interval セカンダリのステータスをチェックするまでの最小待機秒数。 60秒

ほとんどのユーザーにはデフォルトで十分でしょう。変更したい場合は、config/database.yml

production:
  username: gitlab
  database: gitlab
  encoding: unicode
  load_balancing:
    hosts:
      - host1.example.com
      - host2.example.com
    max_replication_difference: 16777216 # 16 MB
    max_replication_lag_time: 30
    replica_check_interval: 30