サービスポータルウィジェットでの遅いクエリを特定して修正するIssue この記事では、特定のサービスポータルウィジェットに関連する遅いクエリを特定して、修正する方法について説明します。この手順は、セルフホスト型のお客様にも該当します。 注意:この記事は、ServiceNow サポートエンジニアおよびセルフホスト型のお客様にのみ適した技術的な詳細情報を提供します。たとえば、クエリの調整やインデックス作成は、ホスティングされているお客様には関連しないトピックです。 この情報は、参照のみを目的として掲載されています。 ReleaseサービスポータルResolutionこの例では、サービスポータルページ /sc_home (サービスカタログのホームページ) の応答が遅く、ロードに 15 秒近くかかっています。また、TTFB (Time To First Byte) を Google Chrome または Microsoft Edge ブラウザのデベロッパーツールで測定すると、高い結果がでます。参考までに、詳細は以下のとおりです。 ページ /sp?id=sc_home (デフォルトのサービスポータルページ) のロード: Microsoft Edge ブラウザと Google Chrome に表示された TTFB を以下に示します。すべての時間が費やされている呼び出しは sc_home が対象です。 問題の再現 maint または admin ユーザーとして、https://instance.service-now.com/sp?id=sc_home に移動すると、最初に報告されたものと同じ動作が確認されました。 ページをロードすると、ページ上に 2 つのウィジェットがあり、一方がもう一方のコピーであり、ウィジェットタイトルのみが変更されていることが確認されました。 アプリケーションノードログ (または SQL デバッグ (詳細) ) を見ると、次の 2 つのパフォーマンスの低いクエリ (黄色で強調表示) があります。 クエリ時間を赤で強調表示し、パフォーマンス低下の原因を青で表示しています。 LIKE 条件は、インデックスで改善できないブラインドクエリを実行します。その結果、次の説明プランに示すように、テーブルのフルスキャンが実行されています。 sc_cat_item.sc_catalog 列は medium 型テキストであり、インデックスは STARTSWITH (文字列の左側でのみが % で囲まれたクエリ) の場合に役立つ可能性がありますが、CONTAINS クエリの使用ではインデックスは役に立たず、完全なテーブルスキャンが確実に実行されます。 検索対象の完全な sys_id が存在することに注意してください (すべて 32 文字)。LIKE 条件を削除して等価にするとどうなるでしょうか。 元のクエリは約 5.4 秒で実行されました。IN 演算子を使用するようにクエリを変更すると、MySQL DB を既存のインデックス作成を使用するように最適化できます。 クエリをリファクタリングすると、正しい答え (結果セット) が提供され、サービスポータルページの呼び出し速度が大幅に向上します。しかし、どのウィジェットが問題であるかを確認し、実際の GlideRecord クエリ自体を取得するにはどうしたらよいでしょうか。 遅いサービスポータルウィジェットを特定する 報告された問題を再現しても、この最適でない SQL クエリがどのウィジェット (クローンウィジェット、ヘッダーウィジェット、または sc_home ページ上の他のサービスポータル要素) で発生しているのかは分かりません。どのウィジェットが原因であるのかを特定するには、ブラウザのコンソールタブから JavaScript コードのスニペットを 2 つ入力する必要があります。 ウィジェットを強調表示するスクリプト ブラウザのコンソールに次のコードスニペットを入力すると、エディターでのウィジェットの強調表示、ウィジェット名、ウィジェットへのリンク、およびウィジェットのスコープをコンソールに出力する機能が有効になります。 ~~~snip~~~ $("div [widget='widget']").css("border", "1px solid red").css("padding-top", "20px").css("position", "relative").each(function(i){ var scope = $(this).scope(); var widget = scope.widget; var elem = $("<div style='position: absolute; top: 1px; left: 1px'><a target='_blank' href='/$sp.do?id=widget_editor&sys_id="+ widget.sys_id+"'> "+ widget.name +"</a> </div>"); var printScope = $("<a href='javascript:void(0);'>Print scope</a>").on('click', function(){ console.info(scope); }); elem.append(printScope); $(this).append(elem); }); ~~~/snip~~~ 特定する手順 この例では、Google Chrome ブラウザを使用します。 調査対象の SP ページを表示した状態で、ブラウザのデベロッパーツールを開きます。(右クリックして [検証] をクリックします。MacOSX では Cmd + Option + J を押します。)[コンソール] タブ (図の #1) をクリックします。[コンソールを消去] ボタン (図の #2) をクリックします。 ウィジェットの強調表示スクリプトを貼り付けます。 スクリーンショットに示すように、ウィジェットすべてに新しい詳細情報がいくつか表示されます(赤い線で囲まれているウィジェット、ウィジェットのタイトル、およびハイパーリンクが付いたウィジェットのタイトル)。 ウィジェットの境界を囲む赤い線とハイパーリンクが付いたタイトルに注目してください。 特定のウィジェットの時間計測値の取得 ウィジェット実行スクリプト (フォーカスされたウィジェット/要素のみ): 次のコードスニペットは、ページ上の 1 つの (強調表示された) ウィジェットを時間を測定しながら実行できるようにします。 ~~~snip~~~ $($0).scope().server.update() ~~~/snip~~~ この例では、引き続き Chrome のデベロッパーツールを使用して、特定のウィジェットの時間計測値を取得します。(数字は図の注意書きを参照しています。) [要素] テーブルをクリックします (#3)。[要素ツール] を選択します (#4)。実行するウィジェットの領域のみを選択し、(#5) でロード時間計測値の詳細を確認します。要素のコード (#6) をクリックして、選択したウィジェットを確定します。[コンソール] タブ (前のスクリーンショットの #1) をクリックします。[コンソールを消去] ボタン (前のスクリーンショットの #2) をクリックします。1 つのウィジェットで実行するために 1 行のコードを貼り付けて、Enter キーを押します。 [ネットワーク] タブをクリックして、1 つのウィジェットの TTFB 時間計測値を確認します。 SC Recent Items ウィジェットのロードにかかる 5.55 秒は、この記事の冒頭で示した低速なクエリの 1 つとほぼ同じです。この sc_home ポータルページには、ページ上に 2 つのウィジェットと 2 つの遅いクエリがありました。次の手順に従って、同じコードを修正できるように、他のどのウィジェットが低速であるかを確認します (この例では、SQL クエリに同じワイルドカード/ブラインドクエリ文字列があるため)。 注 – SQL デバッグ (詳細) を有効にし、仮の UI ページ /monkey.do をロードして、上記の手順でウィジェットのロード中に実行されている遅いクエリを確認できます。 遅い SQL を生成した GlideRecord クエリを見つける 遅いウィジェットの 1 つを確認したので、このウィジェットには問題の SQL を生成する GlideRecord クエリが存在する可能性が十分にあります。エディターでこのウィジェットにアクセスし、問題が見つかるかどうかを確認してから、(変更を加える前に) ウィジェットをバックアップし、クエリを変更して結果をテストします。 前のスクリーンショットに示すように、[SC Recent Items] というハイパーリンク テキストをクリックします。すると、次の図に示すように、ウィジェットエディターで実際のウィジェット (ウィジェットインスタンスではない) が開きます。スクリーンショットの以下の点に注意します。 [サーバースクリプト] チェックボックス (図の #1) のみを選択すると、レビューするサーバー側のコードのみに視覚的に焦点を当てることができます。変更しようとしているコード行 (図の#2) (ただし、この現在のページではありません)。ページの右側にあるコンテキスト メニュー (ハンバーガーアイコン) をクリックし、[プラットフォームでオープン (Open in Platform)] を選択すると、コアプラットフォームでレコードを開くことができます。 ウィジェットをバックアップし、最適でないクエリを変更します。 ウィジェットレコード (コアプラットフォーム内) で、変更を加える前に XML をエクスポートして現在の状態を保存します。 スクリーンショットの 10 行目では、インデックスを使用できないブラインドクエリが発生していることが分かります。(上記の MySQL の説明プランと時間計測値を参照してください。)また、完全な sys_id がクエリで使用され、sys_id 文字列の両側にワイルドカード "%" が付いていることも分かります。IN 範囲の GlideRecord クエリを使用すると、フルテーブルスキャンを実行する代わりに、sys_id の既存のインデックスを使用できます。 次のように、10 行目をコメント化し、コピーして貼り付けます。 From -->count.addQuery('cat_item.sc_catalogs', data,sc_catalog); To -->count.addQuery('cat_item.sc_catalogs', 'IN', data,sc_catalog); 注 – GlideRecord API の詳細については、ServiceNow Developer サイトの「API リファレンス - サーバーサイドのスコーピング (API Reference - Server Side Scoped)」を参照してください。 レコードを保存し、SQL デバッグを有効にしたまま、Google Chrome デベロッパーツールをオンにして、1 つのウィジェットを再ロードします (コンソールで 1 行の JavaScript コードを入力した場合と同様)。ウィジェットのロードが速くなり、クエリ自体が大幅に高速になったことを確認します。 また、SQL デバッグを有効にすると、生成されたばかりの GlideRecord クエリが同等であることが分かります。 2 つ目のサービスポータルウィジェットに対して、同じ修正を適用します。 結果 sc_home ポータルページの 2 つ目のウィジェットは、ウィジェットのヘッダーのラベルが異なることを除いて、最初のウィジェットのクローンでした。そのため、まったく同じ修正を適用して、ポータルページのロード時間を大幅に短縮できます。この手順を使用して遅いクエリを分離し、GlideRecord クエリ条件を変更した後に修正が成功していることを確認します。 代わりの方法としてアクティブクエリの書き換えを使用できますか? はい、ただし、常に期待どおりに動作するとは限らないため、細心の注意を払う必要があります。 sys_id が文字列リテラルとして解釈され、ヒント内の実際の文字列のみが使用される場合があります。(他の sys_id 文字列は使用されません。つまり、変数として扱われません。)これにより、予期しない結果が生じる可能性があります。 また、ウィジェットコードの変更は簡単で、ServicePortal アドミンにとっては明白ですが、アクティブクエリの書き換えは MAINT ロールを持つユーザー (ServiceNow TSE とセルフホスト型のお客様) にのみ表示され、問題を報告したユーザーはプラットフォームの動作が不適切な理由を理解できません。 次の例と詳細は、書き換えの状況を示しています。 また、書き換えにはある程度のオーバーヘッドがあり、ウィジェットでソースクエリを変更するほど迅速ではないことも考慮してください。 次の図は、書き換えによる時間の計測結果を示しています。 注:アクティブ クエリの書き換えを使用する場合は十分に注意してください Related Linksアクティブクエリの書き換え (十分に説明されていない) は、アクティブクエリのインデックスヒント (十分に説明がある) と非常によく似ています。詳細については、「操作手順:「アクティブクエリのインデックスヒント」を使用してクエリ実行の遅さを改善する (Howto: Using "Active Query Index Hints" to improve slow query execution)」を参照してください。