`/presence/update`への無限のリクエスト

バグというほどではないかもしれませんが、デザインされたものかもしれませんが、JS側でプレゼンスがどのように更新されるかについていくつか疑問があります。ファイル app/assets/javascripts/discourse/app/services/presence.js です。

これまでの私の理解では、JSクライアントは以下のようになっています。

  • 更新キューが空でない場合、更新を1秒に1回に制限します。
  • 変更がない場合、30秒に1回の要求を行います。

私が気づいたのは、サーバーが何らかの理由でエラーを返した場合、JSは毎秒リクエストを送信し続けるということです。また、コンソールにエラー Uncaught (in promise) の565行目が発生します。
サーバーに過負荷をかけないように、N回の試行後に停止すべきではないでしょうか?

再現方法

presence_controller.rbupdate メソッドでエラーをシミュレートします。(私の場合は、デモのためにステータス405をレンダリングしました)

  def update

    # JSクライアントは、1秒に1回の要求に制限するように設計されています
    # 変更が行われていない場合、30秒に1回の要求を行います
    RateLimiter.new(nil, "update-presence-#{current_user.id}", 20, 10.seconds).performed!
    render json: { error: "Not authorized" }, status: 405
    return
    ...

その後、クライアントでプレゼンス管理のある画面に移動します。私はトピック返信画面を選択し、返信を開始しました。

結果として、それは永遠に、そして毎秒実行され続けます。

「いいね!」 1

また、プレゼンスの再計算は30秒ごとに本当に必要ですか?なぜなら、以下のような場合があるからです。

  • enter アクションが実行されたときの計算
  • leave アクションが実行されたときの計算
  • ユーザーのプレゼンスが変更されたときの計算

エラー時に「バックオフ」するという機能リクエストですか?そうすれば、エラーが繰り返された場合に最大で約30秒までバックオフできます…

「いいね!」 1

@AhmedLoud は、FIX: use `_presentChannels.size` instead of `_presentChannels.length`… · discourse/discourse@589add7 · GitHub で修正した問題ですか、それとも別の問題ですか?

「いいね!」 1

@david すみません、誤解していました。

私の最初のトピック投稿は、これとは関係ありませんでした: FIX: use `_presentChannels.size` instead of `_presentChannels.length`… · discourse/discourse@589add7 · GitHub

30秒ごとの定期的な更新がなくても、バックエンドからエラーが発生した場合、イベントキューが空にならないため、アルゴリズムは1秒ごとに再試行します。

_scheduleNextUpdateにて

   } else if (this._queuedEvents.length > 0) {
      this._cancelTimer();
      cancel(this._debounceTimer);
      this._debounceTimer = debounce(
        this,
        this._throttledUpdateServer,
        this._presenceDebounceMs // これは1秒だと思います
      );

実際、_updateServerメソッドでは、エラー時にイベントがキューに戻されます。

  } catch (e) {
      // 失敗したイベントを次のためにキューに戻します
      this._queuedEvents.unshift(...queue);

これは後で気づいたことです…すべてがうまくいっている(つまり、エラーがない)場合、30秒ごとにサーバーを更新し続ける必要は本当にありますか?

} else if (
      !this._nextUpdateTimer &&
      this._presentChannels.size > 0 &&
      !isTesting()
    ) {
      this._nextUpdateTimer = discourseLater(
        this,
        this._throttledUpdateServer,
        PRESENCE_INTERVAL_S * 1000 // これは30秒です
      );
    }
「いいね!」 1

バックオフはないと思います。エラーが発生すると、停止せずに1秒ごとに再試行し続けます。しかし、何か見落としたのかもしれません。

「いいね!」 1

なるほど、30秒ごとに「チェックイン」する必要がある理由がわかりました。

1分ごとにトリガーされるバックグラウンドジョブ PresenceChannelAutoLeave があり、期限切れのメンバーをチャンネルから退出させます。

また、presence_channel.rbpresent(user:, client_id:) メソッドのコメントにも記載されています。

    # ユーザーのクライアントがこのチャンネルに存在することをマークします。client_id はブラウザタブごとに一意である必要があります。
    # このメソッドは、ユーザーがチャンネルに存在している間、繰り返し(少なくとも DEFAULT_TIMEOUT ごとに1回)呼び出す必要があります。
    def present(user:, client_id:)

1秒ごとにトリガーされるエラーの部分については、まだ疑問が残っています。

私たちはすでに429「レート制限」エラーを正しく処理していました。しかし、その他の予期せぬエラーについては、指数バックオフロジック(30秒に制限)を追加しました。ご指摘いただきありがとうございます @AhmedLoud

「いいね!」 3

このトピックは9日後に自動的に閉じられました。返信はもうできません。