バックグラウンドスクリプトを作成するための安全に関するヒントDescriptionこの記事の目的は、知識を増やして変更をより安全に行い、潜在的に問題のあるスクリプトをより簡単に見つけられるようにすることですが、これらはすべて、スクリプティングを実行するすべての ServiceNow ユーザーに適用される一般的な情報です。Instructions Table of Contents テクニカルサポートがスクリプトを作成するのはどのような場合か? ルール 1:テクニカルサポートは顧客向けのコードを記述しません。バグで顧客のデータが破損した場合は、クリーンアップを支援する必要があります。問題があると思わる場合にデバッグコードを追加する。カスタムスクリプトを作成することが最も効率的なソリューションでしょうか? .get() と .update() に関する問題 .get() は実際にはレコードをまったく取得していない可能性があります。.get() は複数のレコードを取得している可能性があります。.update()は実際に新しいレコードを挿入している可能性があります。 例: 親 CI のフィールドを更新するために、CI 関係レコードの挿入時に実行されるカスタムビジネスルール代わりに if および .next() を使用したクエリと使用 学んだ教訓: addEncodedQuery と while ループを使用したレコードのループ 悪い例:より良い例: deleteRecord() と deleteMultiple()、およびカスケード削除setWorkflow() および autoSysFields() setWorkflow(false);autoSysFields(false); 「false」は true を意味しますgs.log() と Source パラメータースレッドダンプと正確なタイムスタンプ ユースケースの例:GlideLog.getStackTrace(new Packages.java.lang.Throwable()) 正しいスコープでの実行スケジュール済みスクリプトとして実行し、データセットを分割し、limit() .limit(n) を使用してクエリを一度に n レコードに制限し、スクリプトを複数回実行します。一度に複数のスレッドを実行することで、ジョブを高速化します。 UI アクションまたはビジネスルールをスタンドアロンスクリプトとして実行する例 テクニカルサポートがスクリプトを作成するのはどのような場合か? ルール 1:テクニカルサポートは顧客向けのコードを記述しません。 テクニカルサポートは、顧客向けの機能コードを記述しません。out of the box 機能の既存の設計された動作を拡張、変更、または変更する要件がある場合、それは予定されている機能拡張の一環として開発の業務であり、すべてのユーザー向けのサポートされるコードになります。パートナーやプロフェッショナルサービスが関与する顧客固有の実装はサポートされていないコードです。サポートは、問題のワークアラウンドである場合を除き、顧客向けに out of the box のスクリプトをカスタマイズしません。その場合、更新されたセットに確実にキャプチャされ、修正リリース後、元に戻すための明確な指示が与えられるようにします。サポートは、 out of the box の問題がカスタムスクリプト内のコードによって発生しているかどうかを特定しようとしますが、そのカスタムスクリプトの修正については責任を負いません。1 行の論理エラーや構文エラーなど、非常に単純な場合は、サポートがお客様のカスタムスクリプトを修正する場合があります。また、ベストプラクティスと、カスタマイズによって問題が発生する理由を説明して、お客様が二度と同じことをしない様に努めます。「どうすればよいか」という質問に対して、優れたソリューションアイデアがある場合、サポートは一般的な手順または疑似コードを提供することがありますが、おそらくより良いアイデアを 持っている実装パートナーの支援を得て、アイデアを実装するのは顧客の責任であることが明確になります。サポートはまた、サポート対象外、推奨されていない、アップグレード後に問題が発生する可能性がある、またはメンテナンスが必要であることが判明した場合は、指摘します。サービスはその作業を支援します。これは、顧客がコードの所有権を取得するか、コードに独自のコードを追加する場合に正常に機能します。 バグで顧客のデータが破損した場合は、クリーンアップを支援する必要があります。 顧客インスタンスで実行するためにテクニカルサポートによるスクリプトの記述が必要になる可能性がある例は次のとおりです。 正規化データサービスの問題により、重複するモデルレコードが大量に作成され、CI を元のモデルに戻し、重複するモデルを削除する必要がある。CMDB パーティションごとのテーブル (TPP) の移行の問題により、既存のレコードが識別クエリで表示されないため、アップグレード後に重複 CI のロードが作成されました。重複 CI を特定して削除するには、支援が必要。問題により、親のネットワークインターフェイス (NIC) レコードがない孤立した IP アドレス CI レコードが大量に残される。文書化されていない問題の修正により、余分なモデルカテゴリレコードが追加され、多くの不要な資産レコードが作成されました。これらは CI からリンク解除し、CI をカスケード削除せずに資産を削除する必要がある。 問題があると思わる場合にデバッグコードを追加する。 例: 次のような質問に対する回答を見つけるために gs.log() ステートメントの追加: この行に到達したか?この時点での変数値は何か?値を変更したのは該当の update() か? 行をコメントして、エラーを回避するか、別のエラーを取得するかを確認します。コードのバグを特定したかどうかを確認するための修正 カスタムスクリプトを作成することが最も効率的なソリューションでしょうか? いくつのレコードが関係していますか?代わりに数時間の作業で手動で修正できますか?これを行うためのスクリプトを自分で記述できる自信がありますか?開発またはサポートのエキスパートが関与する必要がありますか?墓穴を掘っている可能性はありますか?次の図を参照してください。- .get() と .update() に関する問題 GlideRecord API 恐ろしいことは次のとおりです。 .get() は実際にはレコードをまったく取得していない可能性があります。 スクリプトが後のコードまたはクエリでそのレコードの値を使用する場合は、想定していた値ではなく「null」(または「undefined」または使用された sys_id 以外の文字列値) を使用している可能性があります。 .get() は複数のレコードを取得している可能性があります。 単一のレコードが取得されているとスクリプトが想定している場合、意図していなかったレコードに対して予期しない処理が行われる可能性があります。 テーブル全体が返される可能性がありますが、これにはアプリノードのメモリにも影響があります。 .update()は実際に新しいレコードを挿入している可能性があります。 .get() がレコードを取得しなかった場合、または壊れた参照を介してレコードにドット連結した場合でも、GlideRecord は初期化されます。 ノードログの「存在しないレコードを取得:...、初期化中」の一般的な警告は、この悪い状況が発生したことを示しています。 スクリプトがこれに関係なく実行されると、その gliderecord をチェックおよび更新せずに挿入が行われます。 例: 親 CI のフィールドを更新するために、CI 関係レコードの挿入時に実行されるカスタムビジネスルール var parentGr = new GlideRecord('cmdb_ci');parentGr.get(current.parent.sys_id.toString());parentGr.u_has_relationship = true;parentGr.update(); これは、関係レコードが存在するためには親と子の両方の CI が既に存在している必要があるという仮定に基づいて書かれています。 cmdb_rel_ci レコードは、親コンピューター CI の挿入前に実行される Discovery の「仮想コンピューターチェック」ビジネスルールによって挿入された可能性があるため、この仮定は誤りです。 これにより、.update() が呼び出されたときに、すべての CI フィールドの値が空白で、親コンピューター CI の挿入が早まります。「実際の」挿入が発生すると、挿入前ビジネスルールが完了した後、sys_id が既に挿入されているために失敗します。 .get() が true を返したことをテストすることで、少し改善されます。 var parentGr = new GlideRecord('cmdb_ci');if (parentGr.get(current.parent.sys_id.toString())) { parentGr.u_has_relationship = true; parentGr.update();} 複数のレコードが返される可能性があるためnull 値を使用すると、テーブル全体を返すことができます。 注意:「.sys_id.toString()」を使用することもお勧めします。JavaScript は異なるオブジェクトタイプ間で自動的にタイプキャストできるはずですが、正しくない場合があります。自分で型変換を行う方が安全です。 .get() には文字列が必要です。これがないと、実際にはメソッドに参照フィールドまたは sys_id フィールドの GlideElement オブジェクトを指定することになります。 代わりに if および .next() を使用したクエリと使用 このアプローチはさらに安全です。.get() の代わりに .query() を実行します次に .next() を使用してレコードを取得します。getRowCount() を使用して、何かを行う前にレコード数が 1 であることを確認することもできます。 var parentGr = new GlideRecord('cmdb_ci');parentGr.addQuery('sys_id', current.parent.sys_id.toString());parentGr.query();if (parentGr.next() && parentGr.getRowCount()==1) { parentGr.u_has_relationship = true; parentGr.update();} 学んだ教訓: 壊れた参照値を使用している可能性があります。前のコードで最初に値が正しく設定されていない場合は null を使用している可能性があります。そのため、次のようになります。 結果が True であることを確認せずに .get() を使用しないでください。クエリの後に if next を使用する方がはるかに安全です。クエリによって返されたレコードをカウント。 addEncodedQuery と while ループを使用したレコードのループ 最新の検出日が 3 か月より前の場合は、すべてのコンピュータークラス CI を [廃止] に設定するという要件があるとします。これらの 8 つのレコード:レコード これを行うには、次のスクリプトを記述します。 var gr = new GlideRecord('cmdb_ci_computer'); gr.query(); while (gr.next()) { if (gr.last_discovered < '2019-04-19') { gr.hardware_status = 'retired'; gr.update(); }} 悪い例: 少なくとも 7 つの大きな問題があります。 CMDB は拡張テーブルです。このクエリには、サーバーなどを含むコンピューターのすべての子テーブルが含まれます。必要以上のレコードをループしています。更新しない GlideRecord オブジェクトレコードをメモリに保持します。すでに廃止されている可能性があるレコードを更新しますが、すでに廃止されているかどうかを最初に確認しません。last_discovered には値があると仮定していますが、これには値のないすべての CI も含まれます。これを実行する頃には、3 か月前は別の日付になっている可能性があります。このスクリプトを関数でラップしていない場合は特に、「gr」は安全な変数名ではありません。 KB0713029を参照してくださいパワーポイントではアポストロフィーがおかしくなってしまいました。 より良い例: リストフィルターに追加することで、上記のすべてのポイントに対処できます。次に、リストと同じテーブル/クエリ文字列で addEncodedQuery() を使用すると、リストとまったく同じレコードを安全にループできます。更新する 8 つのレコードが query() の後の GlideRecord オブジェクトにあるだけでなく、これにより高速になり、メモリ使用量も少なくなります。 var ciGr = new GlideRecord('cmdb_ci_computer'); ciGr.addEncodedQuery('last_discovered<javascript:gs.beginningoflast3months()^hardware_status!=retired^sys_class_name=cmdb_ci_computer^last_discoveredisnotempty');cigr.query();while (cigr.next()) { cigr.hardware_status='retired'; cigr.update();} deleteRecord() と deleteMultiple()、およびカスケード削除 var ciGr = new GlideRecord('cmdb_ci_computer');ciGr.addEncodedQuery('<whatever>');ciGr.query();while (ciGr.next()) { ciGr.deleteRecord(); // not .delete() as you might expect, given update() and insert()!} 次と同等: var ciGr = new GlideRecord('cmdb_ci_computer');ciGr.addEncodedQuery('<whatever>');ciGr.deleteMultiple(); 注意:.query() は deleteMultiple() または UpdateMultiple() では使用されませんが、.query() を使用することを忘れないでください。またはglideRecordオブジェクトに フィルタリングされていないテーブル全体が残っています どちらの場合もカスケード削除が発生し、すべての子レコードが削除され、他のレコードから参照値がクリアされることを忘れないでください。CI の場合は、すべての NIC、ディスク、メモリ、シリアル番号、リンクされた資産に加えて、コスト行などのすべての子レコードに加えて、インシデントや問題などのタスクの影響を受ける CI など、それを参照するものすべてです。 カスケード削除は、削除される CI を参照する可能性がある他のレコードを検索することによって行われます。CMDB を参照するすべてのフィールドにインデックスが必要です。インデックスが欠落していると、削除に非常に時間がかかる可能性があり、関連レコードを持たないめったに使用されないテーブルのチェックにかなりの時間が費やされる可能性があります。削除が遅い場合は、遅い SELECT クエリを確認します。 setWorkflow() および autoSysFields() ビジネスルールとエンジンは、他の挿入/更新/削除と同様に、スクリプトで実行される操作に対して実行されます。 これらは予期しないことや非常に遅いことを行う可能性があります。 たとえば、CI - 資産同期が実行されるため、資産とその親/子も関与します。 setWorkflow(false); 通常は後続のアクションによってトリガーされるビジネスルールの実行を無効にします。 ワークフローエンジンや 他のすべてのエンジンは 実行されないと思います。 監査もオフにします。 autoSysFields(false); sys_updated_by、sys_updated_on、sys_mod_count、sys_created_by、および sys_created_on フィールドの更新を無効にします。 ただし、これは後続のアクションのみです。このスクリプトの次の.update/deleteだけです 例: var ciGr = new GlideRecord('cmdb_ci_computer');ciGr.addEncodedQuery('<whatever>');ciGr.query();while (ciGr.next()) { ciGr.setWorkflow(false); ciGr.autoSysFields(false); ciGr.deleteRecord();}Notes: deleteMultiple() では使用できませんこれらの追加の更新/削除に対しては、カスケード削除と BR の実行が引き続き行われます。sys_class_name が同時に変更された場合、効果はありません。KB0727652 を参照してください 「false」は true を意味します 「false」は単なる文字列値です。ブール値にタイプキャストすると falseではなくtrueになります。正しいデータタイプを使用し、Javascript が自動的にタイプをキャストした後に期待したものになるとは思わないでください。 .setWorkflow(false);は、次の操作で実行中のエンジンをオフにします。.setWorkflow('false'); は次の操作で 実行中のエンジンをオンにします。 少なくとも 30 件のサポートケースが このミスによって発生しています gs.log() と Source パラメーター gs.log() には source の 2 番目のパラメーターがあります。 while (ciGr.next()) { gs.log('Deleting cmdb_ci:' + ciGr.sys_id, 'CS1234567'); ciGr.deleteRecord();} /syslog_list.do は、source=CS1234567 でフィルタリングできます。 スレッドダンプと正確なタイムスタンプ gs.log() ステートメントを複数のセンサーとスクリプトインクルードに追加すると便利な場合がありますが、syslog での作成時間は 1 秒しかないため、シーケンスが混乱します。 getTime() は 1970 年以降のミリ秒を返すため、それをメッセージに含めます: gs.log('Now:' + new Date().getTime() + 'Deleting CI: ' + ciGr.sys_id, 'INT1234567'); 次に、syslog を Excel にエクスポートし、タイムスタンプでソートします。 次の Excel 式を使用して、「Now:」の後のタイムスタンプを抽出できます:=MID(C2,FIND("Now:",C2)+4 ,13) ユースケースの例: MID Server ハートビートのデバッグの例。センサー BR、および 5m のジョブスケジュールにログステートメントが追加されました。 これにより、2 つのアプリノードのシステムクロックが同期していないことが証明されました。ハートビートプローブからの入力は、送信される前に到着します。すべてが同じクロックを使用している場合、これは不可能です。 GlideLog.getStackTrace(new Packages.java.lang.Throwable()) /threads.do は、アプリノードのすべてのスレッドのスレッドダンプを一覧表示します。 オンデマンドで実行されるスレッドに対して同様のスレッドダンプを生成し、ビジネスルールやスクリプトインクルードなどの任意のスクリプトから gs.log に含めることができます。 gs.log('Now:' + new Date().getTime() + 'Stack Trace:\n ' + GlideLog.getStackTrace(new Packages.java.lang.Throwable()), 'INT1234567'); 「検出の実行中にネットワークアダプターを削除するコードは何か?」などの質問に答えるのに最適です。 テーブル cmdb_ci network_adapter の新しい [削除前] ビジネスルールでは、単にスタックトレースを gs.log することができます。 スタックトレースをさかのぼると、.delete() とその下の行が削除を実行していることがわかります。 正しいスコープでの実行 [スクリプト - バックグラウンド] ページ (/sys.scripts.do) から実行する場合、グローバルスコープで実行されると想定する傾向にあるかもしれません。グローバルスコープは常にデフォルトで選択されているためです。 これは、アルファベットの早い方のスコープ名を持つ新しいアプリが追加されると当てはまらないことが多いため、スコープを [グローバル] またはスクリプトを実行する必要があることがわかっているものに切り替えるように注意してください。 スケジュール済みスクリプトとして実行し、データセットを分割し、limit() 顧客向けの Support 作成のデータ修正スクリプトは、長時間またはタイムアウトで実行される可能性があります。 スクリプトをスケジュールスクリプトとして実行する (/sysauto_script.do)[Run] = [On Demand] で [Execute Now] を選択します。時間外に実行するようにスケジュール済みセットアップ後に顧客が自分で実行できるため、サポートは HI の通常の変更による遅延を追加する必要はありません。 .limit(n) を使用してクエリを一度に n レコードに制限し、スクリプトを複数回実行します。 var ciGr = new GlideRecord('cmdb_ci_computer');ciGr.addEncodedQuery('<whatever>');ciGr.limit(1000);ciGr.query();ciGr.deleteMultiple(); クエリの最初の 1000 件の結果のみが含まれます。サブセットでテストし、合計実行時間を見積もるのに適しています。スクリプトが機能することを確認するのに適しています。プロセスで更新/カスケード削除された可能性があるため、ロールバックコンテキストと削除済みレコードを確認します。例:リンクされた資産、変更時に影響を受ける CI 一度に複数のスレッドを実行することで、ジョブを高速化します。 データベースはアプリノードよりもはるかに高速です -> ボトルネックはスケジューラーワーカースレッドです。多くの場合、4 つ以上の CMDB 更新/削除スクリプトを並列実行すると、データベースレプリケーションの遅延が発生します。16のスケジュールスクリプトを 設定します最初にいくつか実行し、データベースのパフォーマンスを確認した後、さらに実行します。 スクリプト 1/16 var ciGr = new GlideRecord('cmdb_ci_computer');ciGr.addQuery('sys_id',''STARTSWITH,'0');ciGr.query();ciGr.deleteMultiple(); スクリプト 16/16 var ciGr = new GlideRecord('cmdb_ci_computer');ciGr.addQuery('sys_id',''STARTSWITH,'f');ciGr.query();ciGr.deleteMultiple(); UI アクションまたはビジネスルールをスタンドアロンスクリプトとして実行する例 「MID スレッドダンプを取得」UI アクション var agent_name = current.name.replace(/'/g, "\'");var midmanage = new MIDServerManage();midmanage.threaddump(agent_name);action.setRedirectURL(current); 「current」は MID Server レコードの GlideRecord、つまり ecc_agent テーブル、およびこの MID Server の sys_id です。これが BR であった場合、 current は実行対象のレコードになります。リダイレクト行は気にせず、次に何をロードするかを UI に指示するだけです。 「current」がないため、代わりにレコードを get() します。 var midGr = new GlideRecord('ecc_agent');if (midGr.get('d55a7c2fdb813300e1943ecf9d961964')) { var agent_name = midGr.name.replace(/'/g, "\'"); var midmanage = new MIDServerManage(); midmanage.threaddump(agent_name);} すべての「稼働中」の MID Server に対して実行しないのはなぜですか? var midGr = new GlideRecord('ecc_agent');midGr.addEncodedQuery('status=Up');midGr.query();while (midGr.next()) { var agent_name = midGr.name.replace(/'/g, "\'"); var midmanage = new MIDServerManage(); midmanage.threaddump(agent_name);} 次に スケジュール設定済みジョブでラップします。 明日この MID Server に戻って、ラッパーログから各スレッドが実行していたコードの行を 10 分ごとに夜間に確認することができます。 これにより、長時間実行されているスレッドが何を実行していたか、どのスレッドが CPU/メモリが高負荷の期間に実行されていたか、それらがどのコード行またはループに含まれていたかを特定できます。 Additional Information次のようなスクリプトに相当するものは何ですか: SELECT name, COUNT(name) AS NumOccurrences FROM cmdb GROUP BY name HAVING ( COUNT(name) > 1 ) ORDER BY NumOccurrences DESC; 回答:GlideAggregate KB0744490「Deleting duplicate records from any table」を参照してください。 (ログインが必要)