優先度/重大度: まれだが致命的
一時的なデータベースエラーの後、最大30分間サイトが使用不能になります。クライアント設定が欠落しているためにJavaScriptエラーが発生し、ページの読み込みが正しく行われません。
プラットフォーム:
- 影響: すべてのDiscourseインスタンス
- 観測された環境: 一時的なデータベース接続の問題が発生している本番環境
説明
実際の結果
一時的なデータベースエラー(接続タイムアウト、プール枯渇、ネットワークグリッチ)が発生すると、そのエラーは30分間キャッシュされます。この間:
- サイトが正しく読み込まれない - ページが破損しているか、機能しないように見えます
- 欠落/無効な設定により、クライアントサイドのJavaScriptエラーが発生します
- すべてのリクエストで「Nil client_settings_json from the cache for ‘client_settings_json\[git_version\]’」がログに記録されます
- 空のクライアント設定がブラウザに返されます
- これはデータベースが完全に回復した後も継続します
期待される結果
一時的なデータベースエラーが発生した場合、システムはエラーをキャッシュすべきではありません。データベースが回復すると、次回の要求でクライアント設定が正常に取得およびキャッシュされ、サイトはすぐに通常の操作を再開する必要があります。
再現手順
- クライアント設定が構成されたDiscourseインスタンスを起動します
- データベース接続の問題をシミュレートします(例:接続プールを枯渇させる、ネットワーク遅延を導入する、またはポート5432を一時的にブロックする)
- SiteSetting.client_settings_json をトリガーするリクエストを行います(どのページロードでもこれを行います)
- エラーを観察します:「Error while generating client_settings_json_uncached: \[database error\]」
- ページが正しくレンダリングされず、JavaScriptコンソールに設定が見つからない関連のエラーが表示されます
- データベース接続を復元します
- 次の30分間にわたって追加のリクエストを行います。データベースは正常でも、「Nil client_settings_json from the cache」エラーが引き続き表示されます
- 30分後、キャッシュが期限切れになり、サイトが最終的に回復します
ユーザーへの影響
この期間中、サイトは事実上ダウンします:
- ページが正しくレンダリングされません
- JavaScriptアプリケーションは、設定が欠落しているため初期化に失敗します
これにより、1秒のデータベースの不具合が30分間のダウンタイムに変わります。
根本原因
lib/site_setting_extension.rb の client_settings_json_uncached メソッドは例外をキャッチし、nil を返します。このnilが30分間キャッシュされ、サイトが破損します。
提案される修正
この問題を修正するためのプルリクエストが提出されました。修正には、nilを返す代わりに例外を再スローする簡単な変更が必要です。
仕組み:
- 例外を再スローすることで、Discourse.cache.fetch がエラーをキャッシュするのを防ぎます
- 外側の client_settings_json メソッドには、キャッシュせずに “”(空文字列)を返す適切な例外処理が既にあります
- データベースの問題中もサイトは機能し続けることができます(ただし、パフォーマンスは低下します)
- データベースが正常になった後、次のリクエストでサイトは自動的に回復します
- この修正により、一時的なデータベースエラーが30分間のダウンタイムを引き起こさないことが保証されます。
影響
このバグは、一時的なデータベースの問題を経験しているすべてのDiscourseインスタンスに影響します:
- トラフィックのスパイク中の接続プール枯渇
- アプリとデータベース間のネットワークグリッチ
- データベースフェイルオーバーシナリオ(例:プライマリ/レプリカの切り替え)
- site_settings テーブルのロック競合
- 遅いクエリによるクエリタイムアウト
重大度: これらの一般的な一時的な問題のいずれかにより、根本的な問題が数秒で解決されたとしても、サイト全体が30分間使用不能になります。
回避策
現在この問題が発生している場合は、キャッシュを手動でクリアすることでサービスを復旧できます:
Railsコンソールで
Discourse.cache.delete(SiteSettingExtension.client_settings_cache_key)