正式なドキュメントは英語版であり、この日本語訳はAI支援翻訳により作成された参考用のものです。日本語訳の一部の内容は人間によるレビューがまだ行われていないため、翻訳のタイミングにより英語版との間に差異が生じることがあります。最新かつ正確な情報については、英語版をご参照ください。

ロングポーリング

  • プラン: Free、Premium、Ultimate
  • 提供形態: GitLab.com、GitLab Self-Managed、GitLab Dedicated

デフォルトでは、GitLab Runnerは新しいCI/CDジョブについて、GitLabインスタンスを定期的にポーリングします。実際のポーリングの間隔は、check_intervalとRunner設定ファイルで設定されたRunnerの数によって異なります

多数のRunnerを処理するサーバーでは、このポーリングにより、次のパフォーマンスの問題が発生する可能性があります:

  • キューイング時間が長くなる。
  • GitLabインスタンスでのCPU使用率が高くなる。

これらの問題を軽減するには、ロングポーリングを有効にする必要があります。

前提要件:

  • 管理者である必要があります。

ロングポーリングを有効にする

GitLabインスタンスを構成して、新しいジョブの準備ができるまで、Runnerからのジョブリクエストをロングポーリングで保持できます。

これを行うには、GitLab Workhorseのロングポーリング時間(apiCiLongPollingDuration)を構成して、ロングポーリングを有効にします:

  1. /etc/gitlab/gitlab.rbを編集します:

    gitlab_workhorse['api_ci_long_polling_duration'] = "50s"
  2. ファイルを保存して、GitLabを再設定します:

    sudo gitlab-ctl reconfigure

gitlab.webservice.workhorse.extraArgs設定でロングポーリングを有効にします。

  1. Helmの値をエクスポートします:

    helm get values gitlab > gitlab_values.yaml
  2. gitlab_values.yamlを編集します:

    gitlab:
      webservice:
        workhorse:
          extraArgs: "-apiCiLongPollingDuration 50s"
  3. ファイルを保存して、新しい値を適用します:

    helm upgrade -f gitlab_values.yaml gitlab gitlab/gitlab
  1. docker-compose.ymlを編集します:

    version: "3.6"
    services:
      gitlab:
        image: 'gitlab/gitlab-ee:latest'
        restart: always
        hostname: 'gitlab.example.com'
        environment:
          GITLAB_OMNIBUS_CONFIG: |
            gitlab_workhorse['api_ci_long_polling_duration'] = "50s"
  2. ファイルを保存して、GitLabを再起動します:

    docker compose up -d

メトリクス

ロングポーリングを有効にすると、GitLab WorkhorseはRedis PubSubチャンネルをサブスクリプションし、通知を待機します。ジョブリクエストは、Runnerキーが変更された場合、またはapiCiLongPollingDurationに達した場合に、ロングポーリングからリリースされます。監視できるPrometheusメトリクスが多数あります:

メトリック説明ラベル
gitlab_workhorse_keywatcher_keywatchersゲージGitLab Workhorseによって監視されているキーの数
gitlab_workhorse_keywatcher_redis_subscriptionsゲージRedis PubSubサブスクリプションの数
gitlab_workhorse_keywatcher_total_messagesカウンターGitLab WorkhorseがPubSubチャンネルで受信したメッセージの総数
gitlab_workhorse_keywatcher_actions_totalカウンターさまざまなキーウォッチャーアクションの数action
gitlab_workhorse_keywatcher_received_bytes_totalカウンターPubSubチャンネルで受信した合計バイト数

あるユーザーがこれらのメトリクスを使用して、ロングポーリングの問題をどのように発見したかの例を確認できます。

ロングポーリングワークフロー

この図は、単一のRunnerがロングポーリングを有効にしてジョブを取得する方法を示しています:

%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
accTitle: Long polling workflow
accDescr: The flow of a single runner getting a job with long polling enabled

    autonumber
    participant C as Runner
    participant W as Workhorse
    participant Redis as Redis
    participant R as Rails
    participant S as Sidekiq
    C->>+W: POST /api/v4/jobs/request
    W->>+Redis: New job for runner A?
    Redis->>+W: Unknown
    W->>+R: POST /api/v4/jobs/request
    R->>+Redis: Runner A: last_update = X
    R->>W: 204 No job, X-GitLab-Last-Update = X
    W->>C: 204 No job, X-GitLab-Last-Update = X
    C->>W: POST /api/v4/jobs/request, X-GitLab-Last-Update: X
    W->>Redis: Notify when last_update change
    Note over W: Request held in long poll
    Note over S: CI job created
    Note over S, Redis: Update all registered runners
    S->>Redis: Runner A: last_update = Z
    Redis->>W: Runner: last_update changed
    Note over W: Request released from long poll
    W->>Rails: POST /api/v4/jobs/request
    Rails->>W: 201 Job was scheduled
    W->>C: 201 Job was scheduled

ステップ1で、Runnerが新しいジョブをリクエストすると、POSTリクエスト(/api/v4/jobs/request)をGitLabサーバーに発行します。これは、最初にWorkhorseによって処理されます。

Workhorseは、X-GitLab-Last-UpdateヘッダーからRunnerトークンと値を読み取り、キーを構築し、そのキーを使用してRedis PubSubチャンネルをサブスクリプションします。キーの値が存在しない場合、WorkhorseはすぐにリクエストをRailsに転送します(ステップ3と4)。

Railsはジョブキューをチェックします。Runnerが使用できるジョブがない場合、Railsは204 No joblast_updateトークンをRunnerに返します(ステップ5〜7)。

Runnerはそのlast_updateトークンを使用し、別のジョブのリクエストを発行して、X-GitLab-Last-Updateヘッダーにこのトークンを入力します。今回、WorkhorseはRunnerのlast_updateトークンが変更されたかどうかを確認します。そうでない場合、WorkhorseはapiCiLongPollingDurationで指定された時間だけリクエストを保持します。

ユーザーが新しいパイプラインまたは実行するジョブをトリガーすると、Sidekiqのバックグラウンドタスクは、ジョブで使用可能なすべてのRunnerのlast_updateの値を更新します。Runnerは、プロジェクト、グループ、インスタンスに登録できます。

ステップ10と11のこの「ティック」は、Workhorseロングポーリングキューからジョブリクエストをリリースし、リクエストがRails(ステップ12)に送信されます。Railsは使用可能なジョブを検索し、そのジョブにRunnerを割り当てます(ステップ13と14)。

ロングポーリングを使用すると、新しいジョブが使用可能になった直後に、Runnerに通知が送信されます。これにより、ジョブのキュー時間を短縮できるだけでなく、新しい作業がある場合にのみジョブリクエストがRailsに到達するため、サーバーのオーバーヘッドも削減されます。

トラブルシューティング

longポーリングを使用している場合、以下の問題が発生する可能性があります。

ジョブのピックアップの遅延

ロングポーリングは、一部のRunner構成では、Runnerがタイムリーにジョブをピックアップしないため、デフォルトでは有効になっていません。issue 27709を参照してください。

これは、Runner config.tomlconcurrent設定が、定義されたRunnerの数よりも低い値に設定されている場合に発生する可能性があります。この問題を解決するには、concurrentの値がRunnerの数以上の値になっていることを確認してください。

config.tomlに3つの[[runners]]エントリがある場合、concurrentが少なくとも3に設定されていることを確認してください。

ロングポーリングが有効になっている場合、Runner:

  1. concurrent個のGoroutineを起動します。
  2. ロングポーリング後にGoroutineが戻るのを待ちます。
  3. 別のリクエストのバッチを実行します。

たとえば、単一のconfig.tomlが構成されている場合を考えてみます:

  • プロジェクトAの3つのRunner。
  • プロジェクトBの1つのRunner。
  • concurrentが3に設定されます。

この例では、Runnerは最初の3つのプロジェクトに対してGoroutineを起動します。最悪の場合、RunnerはプロジェクトAの完全なロングポーリング間隔を待ってから、プロジェクトBのジョブをリクエストする処理に進みます。