データベースの負荷分散
読み取り専用クエリを複数のデータベースサーバー間でディストリビューションします。
概要
データベースのロードバランシングは、複数のコンピューティングリソースにわたるデータベースのワークロードのディストリビューションを改善します。 ロードバランシングは、リソースの使用を最適化し、スループットを最大化し、応答時間を最小化し、単一のリソースの過負荷を回避することを目的としています。 単一コンポーネントの代わりにロードバランシングで複数のコンポーネントを使用すると、冗長性によって信頼性と可用性が向上する可能性があります。ウィキペディアの記事
GitLabでデータベースのロードバランシングが有効になっている場合、Redisのような外部依存関係なしに、シンプルなラウンドロビンアルゴリズムを使ってロードバランシングが行われます。 ロードバランシングはSidekiqでは有効になっていません。これは一貫性の問題につながるためで、Sidekiqはいずれにせよほとんど書き込みを行います。
次の画像では、すべてのセカンダリ(db4
,db5
,db6
)に負荷が均等に分散されていることがわかります。SELECT
のクエリは(必要な場合を除き)プライマリに送信されないため、プライマリ(db3
)にはほとんど負荷がかかりません。
要件
ロードバランシングを行うには、少なくともPostgreSQL 11以降が必要です、MySQLはサポートされていません。また、少なくとも1つのセカンダリをホットスタンバイモードにしておく必要があります。
また、ロードバランシングでは、データベースのフェイルオーバー後も、設定したホストは常にプライマリを指す必要があります。 さらに、ロードバランシングのために追加するホストは、常にセカンダリデータベースを指す必要があります。 つまり、すべてのデータベースの前にロードバランスを置き、GitLabがそれらのロードバランサに接続するようにします。
例えば、プライマリ(db1.gitlab.com
)と2つのセカンダリ(db2.gitlab.com
とdb3.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.com
とhost2.example.com
の間で負荷がバランスされます。
オムニバスのインストールで:
-
/etc/gitlab/gitlab.rb
を編集し、以下の行を追加します:gitlab_rails['db_load_balancing'] = { 'hosts' => ['host1.example.com', 'host2.example.com'] }
-
ファイルを保存し、変更を有効にするために GitLab を再設定します。
ソースからのインストールで:
-
/home/git/gitlab/config/database.yml
を編集し、以下の行を追加または修正してください:production: username: gitlab database: gitlab encoding: unicode load_balancing: hosts: - host1.example.com - host2.example.com
-
ファイルを保存し、GitLabを再起動して変更を有効にします。
サービスディスカバリー
サービスディスカバリーを使うと、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_type
がSRV
に設定されている場合、GitLab は引き続きラウンドロビンアルゴリズムを使用し、レコード内のweight
とpriority
は無視します。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_tcp
をtrue
に設定することで、問い合わせにTCPを使うことができます。
フォーク
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
- 常にログに記録されるコンテキスト情報。例えば、
severity
やtime
。
使用例:
{"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