GlideRecord のパフォーマンスの落とし穴SummaryServiceNow プラットフォームと GlideRecord システムについては、理解すべき点がたくさんあります。パフォーマンスに関連して見られる一般的なスクリプトの問題のいくつかを文書化することが、プラットフォームを使用する開発者に役立つことを願っています。ReleaseすべてのリリースInstructions1)SetLimit関数の使用 ServiceNow 開発者がテーブルをクエリするときに、限られた数のレコードを取得したり、何らかの条件に適合するレコードが存在するかどうかを確認したりすることがよくあります。SQLのデータベースでは、次のようなものを実行します。 SELECT * from MyAwesomeTable WHERE awesome = 1 LIMIT 5 LIMIT 句は、結果セットから 5 つのレコードのみを選択するようにデータベースに指示します。GLIDEからも同じ機能を実現できます。これを行うには、GlideRecord で「SetLimit」関数を使用します。開発者は、大きなテーブルをクエリして 1 つの結果を検索する場合や、結果のサイズが不明なクエリを検索する場合は常に、この機能を利用する必要があります。また、これを使用して、結果を一覧表示するページの結果を制限し、ページネーションを実現する必要があります。スクリプトでも、このメリットは、スクリプトが CMDB のような大きなデータセットに対してクエリを実行している可能性がある場合は、setLimit を使用して結果をページネーションできます。以下は、いくつかの一般的な使用法と落とし穴です。 基本的な使用法 元のコード //Let's just print all of the active incidents.var active_incidents = new GlideRecord('incident');active_incidents.addActiveQuery();active_incidents.query();while(active_incidents.next()){ gs.print(active_incidents.number);}*** Script: INC0017107*** Script: INC0016450*** Script: INC0016540*** Script: INC0014179*** Script: INC0016960*** Script: INC0010001*** Script: INC0017116*** Script: INC0016844*** Script: INC0017161*** Script: INC0015941*** Script: INC0010015*** Script: INC0010009*** Script: INC0017223*** Script: INC0015824*** Script: INC0016746*** Script: INC0017242*** Script: INC0014404*** Script: INC0016514*** Script: INC0016743*** Script: INC0010003*** Script: INC0010024*** Script: INC0016802*** Script: INC0017323*** Script: Demo_INC0010033*** Script: INC0014338*** Script: INC0014271*** Script: INC0013334*** Script: INC0010004***SNIP*** 制限コードを設定 //You know what? That was too many, let's print one active incidentvar active_incidents = new GlideRecord('incident');active_incidents.addActiveQuery();active_incidents.setLimit(1);active_incidents.query();while(active_incidents.next()){ gs.print(active_incidents.number);}*** Script: INC0017107**DONE** 1 つのレコードを確認しています これは、私たちが支持で目にする一般的な状況です。多くの開発者は、更新前に存在するレコードがあるかどうかを確認するために、次のようなことを思いつきます。簡単なサンプルコードを記述してから、表示されるいくつかの一般的な問題を確認します。 //The idea behind the following is that all VM's have a special_identifier and that identifier will only have one group associated to it. //So we can query an already existing vm with that identifier and use it's group for our new VM.function addNewVM(new_vm){ var virtual_machines = new GlideRecord('cmdb_ci_vm'); virtual_machines.addQuery('status', 'Operational'); virtual_machines.addQuery('u_special_identifier', new_vm.u_special_identifier); virtual_machines.query(); if(virtual_machines.next()) new_vm.u_special_group = virtual_machines.u_special_group new_vm.u_special_group.update();} 実際には、上記について予想していなかったいくつかの問題があります。new_vm特殊識別子が NULL の場合はどうなりますか?ServiceNow プラットフォームは引き続きそのクエリを実行し、以下を取得します。 SELECT * from cmdb_ci_vm WHERE status = 'Operational' and u_special_identifier IS NULL u_special_identiferのない VM がある場合、実際には 1 つのレコードしか見ていないにもかかわらず、プラットフォームが移動してリスト全体を取得する大規模なクエリを取得する可能性があります。もう1つの考えられる問題は、時間の経過に伴う成長です。最初は、グループ内には数台のVMしかありません。選択数が少ないため、このクエリは非常に迅速に実行されます。時が経つにつれて、私たちはこれらのグループを拡大し、今ではグループには何千人ものメンバーがいます。上記では、数千のレコードに対してクエリを実行して、1 つのレコードのみを確認します。そのため、多くの無駄な処理が発生します。これらの問題を防ぐために、上記のコードを変更する必要があります。 function addNewVMToGroup(new_vm){ //If our new vm does not have an identifier, then don't even try this if(new_vm.u_special_identifier == null) return; var virtual_machines = new GlideRecord('cmdb_ci_vm'); virtual_machines.addQuery('status', 'Operational'); virtual_machines.addQuery('u_special_identifier', new_vm.u_special_identifier); //We only need 1 record so let's just get 1. virtual_machines.setLimit(1); virtual_machines.query(); if(virtual_machines.next()){ new_vm.u_special_group = virtual_machines.u_special_group new_vm.update(); }} 上記では、特別な識別子の両方がNULLであり、探しているグループに何千もの結果がある場合でも、1つのレコードしか取得されません。 スクリプト結果のページネーション 非常に大きな結果セットを反復処理する必要があるスクリプトまたは REST サービスがある場合は、setLimit 関数を使用してそれらの結果をバッファリングし、システムまたはエンドポイントのいずれかを圧倒しないようにすることができます。 //This function will migrate vm's from one group to the otherfunction addNewVMToGroup(old_group, new_group){ //set our limit count var LIMIT = 10000; //Get a count of all the records we will be migrating var count = new GlideAggregate('cmdb_ci_vm'); count.addAggregate('COUNT'); count.addQuery('status', 'Operational'); count.addQuery('u_special_group', old_group); count.query(); var q_count = 0; if (count.next()) q_count = count.getAggregate('COUNT'); while(count > 0){ var virtual_machines = new GlideRecord('cmdb_ci_vm'); virtual_machines.addQuery('status', 'Operational'); virtual_machines.addQuery('u_special_group', old_group); //We could be moving a large group lets limit the result set. virtual_machines.setLimit(LIMIT); virtual_machines.query(); while(virtual_machines.next()){ virtual_machines.u_special_group = new_group; virtual_machines.update(); } count--; }} 最新または最も古いレコードの取得 一般的には、何らかの日付値順に並べられたレコードを確認します。この例では、最も古いレコードまたは最新のレコードが必要であるという単純なアイデアを見ていきますが、このアイデアは、開始または終了が必要な場合のほぼすべての日付フィールドによる順序付けに適用されます。以下の例では、setLimit を含めていなかった場合、テーブル全体を元に戻すことになります。setLimit を使用すると、フィールドがインデックス化されている場合、MariaDB の最適化によりクエリがさらに高速に返されるという利点があります。 function getOldestVm(){ var virtual_machines = new GlideRecord('cmdb_ci_vm'); virtual_machines.orderBy('sys_created_on'); //We only need 1 record so let's just get 1. virtual_machines.setLimit(1); virtual_machines.query(); if(virtual_machines.next()){ return virtual_machines.sys_id; }} 2) 常に NULL をチェックする 使用する前に、GlideRecord addQuery() または addEncodedQuery() 関数で使用する値が実際に値を持っていることを常に確認する必要があります。値がないと、クエリの実行が遅くなり、予期しない結果が生じる可能性があります。 たとえば、次のコードは、タスクテーブルの [correlation_id] フィールドをフィルタリングします。 var gr = new GlideRecord('task');gr.addQuery('correlation_id', pValue);gr.query(); 開発者は、これで 1 つのレコードが返されることを予期していたかもしれません。ただし、 pValue の値が NULL の場合、次のようなクエリが生成されます。 SELECT task0.`sys_id` FROM task task0 WHERE task0.`correlation_id` IS NULL ...そして、これは実際には数百、数千、またはそれ以上のレコードを返す可能性があり、これらはcorrelation_id値と一致しないため、コードのロジックが壊れる可能性が最も高いです。処理する前に、常に入力変数を確認することをお勧めします。例: if (pValue) { var gr = new GlideRecord('task'); gr.addQuery('correlation_id', pValue); gr.query(); } または、以下を使用することもできます。 if (!JSUtil.nil(pValue)) { var gr = new GlideRecord('task'); gr.addQuery('correlation_id', pValue); gr.query(); } または: if (!gs.nil(pValue)) { var gr = new GlideRecord('task'); gr.addQuery('correlation_id', pValue); gr.query();} 3)コードの誤植を避ける Javascript は容赦がなく、間違ったフィールド名を指定してもエラーは発生しません。ServiceNow プラットフォームはエラーをスローしますが、これは localhost ログのみです。その場合でも、コードは引き続き実行されます。たとえば、次のコードを参照してください。 var pValue;var gr = new GlideRecord('task');gr.addQuery('correlation_id_oops', pValue);gr.query(); 次のようなクエリが得られます。 Oh no! We are pulling the entire task table!!!SELECT task0.`sys_id` FROM task task0 そして、以下がログに記録されます。 QueryEventLogger: Invalid query detected, please check logs for details [Unknown field null in table task]Invalid query detected, stack trace below [Unknown field null in table task]Query [correlation_id_oops] これにより、バックエンドでクエリが遅くなったり、予期しない結果が生成されたりする可能性があります。Related Linksスクリプトでテーブルをクエリする方法 setLimit 関数、SetLimit ドキュメントに関するドキュメント。