JunshuO
ServiceNow Employee

時間とスペースの複雑さを考慮した ServiceNow のパフォーマンスの向上

私たちは、再利用可能なスクリプトインクルード、スコープ対象のアプリ、ACL、スケーラブルな設計など、コーディングのベストプラクティスを重視することがよくあります。しかし、1つの重要な側面が見落とされています。それは アルゴリズム効率 です。

ブルートフォースなコードは、要件を迅速に満たすことができる一方で、データが増加するにつれてパフォーマンスが低下する可能性があります。非効率的なスクリプトは、1人のユーザーの速度を低下させるだけではありません。セマフォノードを消費するため、 インスタンス全体のレイテンシや機能停止につながる可能性があります。

インスタンスのスケーリングは、スクリプトインクルードやスコープ対象アプリだけではありません。それは 計算効率 に関するものです。
この投稿では、基本的なコーディング標準を超えて、 Big O 表記法、JavaScript オブジェクト、関数型プログラミング の世界を深く掘り下げ、インスタンスが超高速に維持されるよう確実にします。

ネストされたループを書いて2つのデータセットを比較するとき、私たちは単に「悪いコード」を書いているのではなく、 O(n^2) の時限爆弾を作成しているのです。CMDBが1,000レコードから100,000レコードに増加するにつれて、そのスクリプトは多少遅くなるだけにとどまらず、アプリケーションノード全体をフリーズさせる可能性がある指数関数的な負荷になります。


次のような恐ろしいエラーを目にしたことがあるでしょう。

「トランザクションがキャンセルされました:最大実行時間を超えました。」

 

これは、スクリプトが5分のクォータを超えて実行された場合に発生し、多くの場合、ネストされたループや過剰なGlideRecordクエリがその原因となっています。

シナリオ:「監査と更新」

要件: 5,000件の「サーバーステータス」更新を含む外部ステージングテーブルがあります。cmdb_ci_serverテーブル(150,000件のレコードを含む)内で対応するCIを見つけ、「最終確認」の日付を更新する必要があります。

 

->アプローチA: ブルートフォース (ネストされた GlideRecord クエリ):

このアプローチは「論理的」ですが、アーキテクチャ的には悲惨なものです。ネストされたクエリパターンを使用します。

// O(N * M) Complexity - The "Transaction timeout"
var staging = new GlideRecord('u_server_updates');
staging.query();

while (staging.next()) {
    // For EVERY staging record, we hit the database again
    var ci = new GlideRecord('cmdb_ci_server');
    ci.addQuery('serial_number', staging.u_serial); 
    ci.query(); 
    
    if (ci.next()) {
        ci.u_last_checked = new GlideDateTime();
        ci.update();
    }
}


問題: ステージング行が5,000行ある場合、このスクリプトは**5,001件のデータベースクエリ**を実行します。データベースの「ハンドシェイク」オーバーヘッドだけでも、トランザクションの制限を超える可能性があります。

複雑さ: O(n*m)


->アプローチB:最適化されたソリューション(JavaScriptオブジェクトパターン)

ここでは、 空間と時間のトレードオフ を適用します。速度(時間)を大幅に向上させるために、やや多くのメモリ(空間)を使用します。

// O(N + M) Complexity - The "Scalable" Approach
var serverMap = {};
var serials = [];

// 1. Collect all serials first - O(N)
var staging = new GlideRecord('u_server_updates');
staging.query();
while (staging.next()) {
    var s = staging.getValue('u_serial');
    serials.push(s);
    serverMap[s] = true; // Use a JavaScript Object for O(1) lookups later
}

// 2. Perform ONE bulk query - O(M)
var ci = new GlideRecord('cmdb_ci_server');
ci.addQuery('serial_number', 'IN', serials);
ci.query();

while (ci.next()) {
    // 3. Constant time lookup
    if (serverMap[ci.serial_number]) {
        ci.u_last_checked = new GlideDateTime();
        ci.setWorkflow(false); // Optimization: Skip Business Rules
        ci.update();
    }
}

結果 :**5,000件のクエリを大幅に削減しました。**

複雑さ : O(n + m) です。このスクリプトは数分ではなく、数秒で終了します。

->パフォーマンスのベストプラクティス:

GlideRecord クエリの最小化 :addQuery、addExtraFields、および一括クエリを使用します。

ネストされたループを避ける :ハッシュマップまたはセットに置き換えます。

フィールドのインデックス化 :テーブル全体のスキャンを回避するために、インデックス付きフィールドに対してクエリを実行します。

非同期メカニズムによるバッチ処理 :大規模なデータセットの場合は、スクリプトアクションをeventSchedulerまたはスケジュール済みジョブとともに使用します。

複雑さの測定 :コーディングする前にBig O表記で考えてください。

 

判断

効率は「あればいい」ものではなく、企業の安定性の要件です。テーブルが100万行に達したときにスクリプトがどのように動作するかを考えていないのであれば、将来のために構築しているわけではなく、技術的負債を積み上げているだけです。

 

ServiceNow プロフェッショナルとしての私たちの仕事は、[送信] ボタンを機能させることだけではありません。私たちの仕事は、会社が成長するにつれてシステムを高速に保つことです。

次回スクリプトを書くときは、次のように自問してください。

  • このループ内にクエリはありますか? (目標:0)

  • 不要なフィールドをフェッチしていますか?

  • ネストされたループの代わりにJavaScriptオブジェクトまたはSet Differenceを使用できますか?

パフォーマンスは最後に追加する機能ではありません。それは最初に適用する考え方です。



ありがとうございます。
ラトナカール


この記事は機械翻訳されております。最新は元となる記事をご覧ください: https://www.servicenow.com/community/developer-advocate-blog/beyond-best-practices-developer-s-guide...。

免責事項: 一部の日本語は、翻訳ソフトウェアを使用してお客様の便宜のために翻訳されています。正確な翻訳をご提供できるよう相当な努力を払っておりますが、いかなる自動翻訳も人間の翻訳者に代わすることはなく、そのようなことは意図されておりません。翻訳は「現状のまま」提供されています。他言語への翻訳の的確性、信頼性または正確性については、明示または黙示を問わず、いかなる保証も行われません。翻訳ソフトには限界があるため、一部のコンテンツが正確に翻訳されていない場合があります。これらの資料の公用言語は英語です。翻訳の際に生じる相違または不一致は、コンプライアンスまたは履行の目的に関しては拘束力を有さず、法的効力はないものとします。

This article was machine translated and replicated from the original source: https://www.servicenow.com/community/developer-advocate-blog/beyond-best-practices-developer-s-guide...。