長文(約10万字)の投稿で大幅な編集を保存する際にDiffボトルネックによりバックエンドで502/504タイムアウトが発生

10万文字程度に及ぶ非常に長い投稿の編集と保存時に、バックエンドのタイムアウト問題が発生しました。保存操作中にサーバーが応答しなくなり、502/504 エラーが発生します。フロントエンドのコンソールには以下のエラースタックが表示されます。

ajax-error.js:36:15
l ajax-error.js:36
u ajax-error.js:75
d ajax-error.js:84
Ember 41
update rest.js:72
update rest.js:72
save rest.js:115
editPost composer.js:1147
Ember 6

いくつかの比較テストを行ったところ、ボトルネックは新旧バージョン間の差分計算にあることが示唆されました。

  • 長い投稿 A を直接編集して長い投稿 B に変更し、保存すると、常に 502/504 タイムアウトが発生します。
  • 長い投稿をいったんクリアし、短いプレースホルダー(例:5 文字)を保存してから、新しいコンテンツ B 全体を貼り付けて保存すると、すぐに完了します。

現在の Diff エンジンは、非常に長いテキストと変更率の高さが組み合わさった極端なケースに対応しにくいようです。パフォーマンスのフォールバック機構を追加することは可能でしょうか?例えば、テキストが非常に長く、変更率が高い場合、システムは詳細な行ごとの差分計算を行う代わりに、「完全書き換え」として扱うようにするといった方法です。

チームには、大規模な投稿に対する Diff 処理の最適化計画や、そのようなシナリオ向けのグラセフルデグラデーション(優雅な劣化)/保護機能の導入計画はありますか?もう一つのアイデアとして、まず保存を成功させ、その後非同期で差分計算を行うようにするというものです。

その間、サーバー側のUnicornログにはタイムアウトの正確な時点が記録され、マークダウンの差分処理中にワーカーが強制終了されたことが確認されました。

Unicorn worker received USR2 signal indicating it is about to timeout, dumping backtrace for main thread
config/unicorn.conf.rb:204:in `backtrace'
config/unicorn.conf.rb:204:in `block (2 levels) in reload'
/var/www/discourse/lib/discourse_diff.rb:172:in `[]'
/var/www/discourse/lib/discourse_diff.rb:172:in `tokenize_markdown'
/var/www/discourse/lib/discourse_diff.rb:115:in `side_by_side_markdown'
/var/www/discourse/app/serializers/post_revision_serializer.rb:128:in `body_changes'
...