mcpField
P
performance-optimization

performance-optimization

github.com/happy-technologies-llc開発4143 回インストール3 日前に更新
リポジトリ

performance-optimization について

ServiceNowの開発者、管理者、パフォーマンスエンジニア向けの最適化スキルです。診断手順に加え、クエリ、インデックス、スクリプト、クライアント側の性能改善を扱います。

主な機能

  1. 01遅いトランザクションとクエリの特定
  2. 02GlideRecordクエリの最適化
  3. 03N+1データベース呼び出しの回避
  4. 04インデックスの確認と作成
  5. 05ビジネスルールとクライアントスクリプトの調整

活用シーン

  1. 01フォームやリストの表示遅延の調査
  2. 02クエリとトランザクション時間の短縮
  3. 03スクリプト内の重複したDB呼び出しの確認
  4. 04インデックスとビジネスルールのボトルネック評価

MCP 対応

SN-Query-TableやSN-Execute-Background-ScriptなどのServiceNow MCPツールを使った性能分析や変更の例が含まれています。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/happy-technologies-llc/happy-platform-skills --skill performance-optimization

インストール後、Claude Code で/performance-optimizationを入力して読み込まれたか確認します。

詳細

ServiceNow パフォーマンスの最適化

概要

このスキルでは、ServiceNow のパフォーマンス問題を特定して解決するための包括的なアプローチを紹介します。

  • ボトルネックの特定: トランザクションログ、低速なクエリ、セッションデバッグの活用
  • GlideRecord の最適化: 効率的なデータベース操作のベストプラクティス
  • クエリのチューニング: N+1 クエリの回避、適切なフィルタリング、結果の制限
  • インデックス管理: 効果的なデータベースインデックスの作成と管理
  • キャッシュ戦略: ServiceNow のキャッシュ機構の活用
  • クライアント側のパフォーマンス: UI スクリプトとフォーム読み込みの最適化
  • プロファイリングツール: セッションデバッグ、トランザクションクォータ、モニタリング

使用するタイミング: フォームの読み込みが遅い、リストビューの表示に時間がかかる、ビジネスルールがタイムアウトする、またはシステム全般の動作が遅い場合に使用します。

対象ユーザー: ServiceNow の最適化を担当する開発者、管理者、パフォーマンスエンジニアです。

期待される成果: フォームの読み込み時間の短縮、トランザクション時間の削減、ユーザーエクスペリエンスの向上、システムリソース消費の低減です。

前提条件

  • ロール: admin(インデックス作成用)、itil または developer(スクリプト分析用)
  • アクセス権: sys_slow_transaction、syslog_transaction、sys_db_index テーブル
  • 知識: GlideRecord API、JavaScript、ServiceNow アーキテクチャ
  • ツール: セッションデバッグ機能、Stats.do ページへのアクセス
  • 環境: 最適化をテストするための本番環境以外のインスタンス

パフォーマンス指標のベースライン

主要業績評価指標(KPI)
指標目標警告重大
フォーム読み込み時間< 2 sec2-5 sec> 5 sec
リストビューの読み込み時間< 3 sec3-7 sec> 7 sec
ビジネスルールの実行時間< 100 ms100-500 ms> 500 ms
クライアントスクリプトの実行時間< 200 ms200-500 ms> 500 ms
クエリ応答時間< 50 ms50-200 ms> 200 ms
API 応答時間< 1 sec1-3 sec> 3 sec

手順

フェーズ 1:パフォーマンス分析とボトルネックの特定
ステップ 1.1:低速なトランザクションを検索する

インスタンス内で最も時間のかかるトランザクションを特定します。

MCP を使用:

Tool: SN-Query-Table
Parameters:
  table_name: sys_slow_transaction
  query: sys_created_on>javascript:gs.daysAgo(7)
  fields: url,response_time,user,client_transaction,sys_created_on
  limit: 50
  orderBy: response_time
  orderByDesc: true

結果の分析:

  • 応答時間が 10,000 ms を超える場合は、深刻な問題を示しています
  • パターンを確認します。同じ URL、同じユーザー、同じ時間帯
  • client_transaction の値が高い場合は、クライアント側の問題が考えられます
ステップ 1.2:低速なクエリを特定する

過剰な時間を消費しているデータベースクエリを見つけます。

MCP を使用:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Query slow database operations
    var slowQueries = [];

    var gr = new GlideRecord('syslog_transaction');
    gr.addQuery('sys_created_on', '>', gs.daysAgo(1));
    gr.addQuery('type', 'sql');
    gr.orderByDesc('response_time');
    gr.setLimit(50);
    gr.query();

    while (gr.next()) {
      slowQueries.push({
        table: gr.url.toString().split(' ')[0],
        response_time: gr.response_time.toString(),
        query: gr.url.toString().substring(0, 200),
        created: gr.sys_created_on.toString()
      });
    }

    gs.info('Slow Queries Report:\n' + JSON.stringify(slowQueries, null, 2));
  description: Identify slow database queries
ステップ 1.3:セッションデバッグを有効にする

トランザクションを詳細に分析するには、次の手順を実行します。

手動での手順(UI が必要):

  1. System Diagnostics > Session Debug > Debug Business Rule に移動します
  2. 関連するデバッガーを有効にします。
    • Debug Business Rule (SQL)
    • Debug Log
    • Debug SQL (Detailed)
  3. 低速な操作を再現します
  4. Debug Log セクションで出力を確認します

クエリデバッグの出力:

Tool: SN-Query-Table
Parameters:
  table_name: syslog
  query: sourceLIKEDebug^sys_created_on>javascript:gs.minutesAgo(15)
  fields: message,source,sys_created_on
  limit: 100
ステップ 1.4:トランザクションクォータを監視する

クォータ違反がないか確認します。

MCP を使用:

Tool: SN-Query-Table
Parameters:
  table_name: syslog
  query: messageLIKEquota^sys_created_on>javascript:gs.daysAgo(1)
  fields: message,source,sys_created_on
  limit: 50

一般的なクォータ違反:

クォータデフォルトの上限症状
SQL ステートメント1,000 per transactionN+1 クエリパターン
スクリプト実行180 seconds実行時間の長いスクリプト
メモリ100 MB大量のデータ処理
アウトバウンド HTTP10 per transactionインテグレーションループ
フェーズ 2:GlideRecord の最適化
ステップ 2.1:setLimit() の基本的な使い方

テーブル全体のスキャンを防ぐため、結果には常に上限を設定します。

// BAD: No limit - scans entire table
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.query();

// GOOD: Explicit limit
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.setLimit(100);  // Only get what you need
gr.query();

上限を使い分ける場合:

用途推奨される上限
UI リストへの表示20-50
バッチ処理100-500
集計の入力1000 max
存在確認1
ステップ 2.2:setFields() でフィールドを選択する

必要なフィールドのみを取得します。

// BAD: Gets all 100+ fields
var gr = new GlideRecord('incident');
gr.addQuery('priority', 1);
gr.query();

// GOOD: Gets only needed fields (70%+ faster)
var gr = new GlideRecord('incident');
gr.addQuery('priority', 1);
gr.setFields('sys_id,number,short_description,assigned_to');
gr.query();

MCP を使用:

Tool: SN-Query-Table
Parameters:
  table_name: incident
  query: active=true^priority=1
  fields: sys_id,number,short_description,assigned_to
  limit: 50
ステップ 2.3:getRowCount() で存在を確認する

単純な存在確認では、反復処理を避けます。

// BAD: Loads record just to check existence
var gr = new GlideRecord('incident');
gr.addQuery('number', 'INC0012345');
gr.query();
if (gr.next()) {
  // Exists
}

// GOOD: Use getRowCount() for existence
var gr = new GlideRecord('incident');
gr.addQuery('number', 'INC0012345');
gr.setLimit(1);
gr.query();
if (gr.getRowCount() > 0) {
  // Exists
}

// BEST: Use get() for single record by unique field
var gr = new GlideRecord('incident');
if (gr.get('number', 'INC0012345')) {
  // Exists and loaded
}
ステップ 2.4:カウントには GlideAggregate を使う

レコード数を数えるために反復処理を行ってはいけません:

// BAD: Loads all records just to count (can be 1000x slower)
var count = 0;
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.query();
while (gr.next()) {
  count++;
}

// GOOD: Use GlideAggregate
var ga = new GlideAggregate('incident');
ga.addQuery('active', true);
ga.addAggregate('COUNT');
ga.query();
var count = 0;
if (ga.next()) {
  count = parseInt(ga.getAggregate('COUNT'));
}

高度な集計の例:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Get incident counts grouped by priority
    var stats = {};

    var ga = new GlideAggregate('incident');
    ga.addQuery('active', true);
    ga.addAggregate('COUNT');
    ga.groupBy('priority');
    ga.query();

    while (ga.next()) {
      var priority = ga.priority.getDisplayValue() || 'Unset';
      var count = parseInt(ga.getAggregate('COUNT'));
      stats[priority] = count;
    }

    gs.info('Incident Stats by Priority:\n' + JSON.stringify(stats, null, 2));
  description: Count incidents by priority using GlideAggregate
フェーズ 3:N+1 クエリの回避
ステップ 3.1:N+1 問題を理解する

N+1 アンチパターン:

// BAD: N+1 query pattern (1 + N database calls)
var incidents = new GlideRecord('incident');
incidents.addQuery('active', true);
incidents.setLimit(100);
incidents.query();

while (incidents.next()) {
  // This causes a separate query for EACH incident
  var user = new GlideRecord('sys_user');
  user.get(incidents.assigned_to);  // +1 query per iteration
  gs.info(incidents.number + ' - ' + user.name);
}
// Total queries: 1 + 100 = 101 queries!
ステップ 3.2:解決策 - ドットウォーキング

参照フィールドにはドットウォーキングを使います:

// GOOD: Single query with dot-walking
var incidents = new GlideRecord('incident');
incidents.addQuery('active', true);
incidents.setLimit(100);
incidents.query();

while (incidents.next()) {
  // Dot-walking retrieves reference data without extra queries
  var userName = incidents.assigned_to.name;
  var userEmail = incidents.assigned_to.email;
  gs.info(incidents.number + ' - ' + userName);
}
// Total queries: 1 query!
ステップ 3.3:解決策 - バッチ読み込み

複雑なケースでは、すべての参照先を先に読み込みます:

// GOOD: Batch loading pattern
// Step 1: Collect all unique sys_ids
var userIds = [];
var incidents = new GlideRecord('incident');
incidents.addQuery('active', true);
incidents.addQuery('assigned_to', '!=', '');
incidents.setLimit(100);
incidents.query();

while (incidents.next()) {
  var userId = incidents.assigned_to.toString();
  if (userIds.indexOf(userId) === -1) {
    userIds.push(userId);
  }
}

// Step 2: Load all users in single query
var userMap = {};
if (userIds.length > 0) {
  var users = new GlideRecord('sys_user');
  users.addQuery('sys_id', 'IN', userIds.join(','));
  users.query();
  while (users.next()) {
    userMap[users.sys_id.toString()] = {
      name: users.name.toString(),
      email: users.email.toString()
    };
  }
}
// Total queries: 2 queries regardless of record count!
ステップ 3.4:検出スクリプト

コード内の N+1 パターンを検出します:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Analyze business rules for potential N+1 patterns
    var suspects = [];

    var gr = new GlideRecord('sys_script');
    gr.addQuery('active', true);
    gr.addEncodedQuery('scriptLIKEwhile*next*^scriptLIKEnew GlideRecord');
    gr.setLimit(50);
    gr.query();

    while (gr.next()) {
      // Check for GlideRecord inside while loops
      var script = gr.script.toString();
      var lines = script.split('\n');
      var inWhileLoop = false;
      var hasInnerGR = false;

      for (var i = 0; i < lines.length; i++) {
        if (lines[i].indexOf('while') > -1 && lines[i].indexOf('.next()') > -1) {
          inWhileLoop = true;
        }
        if (inWhileLoop && lines[i].indexOf('new GlideRecord') > -1) {
          hasInnerGR = true;
          break;
        }
        if (inWhileLoop && lines[i].indexOf('}') > -1) {
          inWhileLoop = false;
        }
      }

      if (hasInnerGR) {
        suspects.push({
          name: gr.name.toString(),
          table: gr.collection.toString(),
          sys_id: gr.sys_id.toString()
        });
      }
    }

    gs.info('Potential N+1 Patterns Found: ' + suspects.length);
    gs.info(JSON.stringify(suspects, null, 2));
  description: Detect potential N+1 query patterns in business rules
フェーズ 4:インデックスの最適化
ステップ 4.1:不足しているインデックスを特定する

インデックスのない、頻繁にクエリされるフィールドを見つけます:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Analyze slow queries for missing indexes
    var fieldUsage = {};

    var gr = new GlideRecord('syslog_transaction');
    gr.addQuery('type', 'sql');
    gr.addQuery('sys_created_on', '>', gs.daysAgo(1));
    gr.addQuery('response_time', '>', 100);  // > 100ms
    gr.setLimit(500);
    gr.query();

    while (gr.next()) {
      var url = gr.url.toString();
      // Extract WHERE clause fields
      var whereMatch = url.match(/WHERE\s+(.+?)(?:ORDER|LIMIT|$)/i);
      if (whereMatch) {
        var conditions = whereMatch[1].split(/\s+AND\s+/i);
        conditions.forEach(function(cond) {
          var fieldMatch = cond.match(/^(\w+)/);
          if (fieldMatch) {
            var field = fieldMatch[1];
            fieldUsage[field] = (fieldUsage[field] || 0) + 1;
          }
        });
      }
    }

    // Sort by usage
    var sorted = Object.keys(fieldUsage).sort(function(a,b) {
      return fieldUsage[b] - fieldUsage[a];
    });

    gs.info('Most queried fields (potential index candidates):');
    sorted.slice(0, 20).forEach(function(field) {
      gs.info('  ' + field + ': ' + fieldUsage[field] + ' queries');
    });
  description: Identify index candidates from slow queries
ステップ 4.2:既存のインデックスを確認する

テーブルの現在のインデックスを確認します:

Tool: SN-Query-Table
Parameters:
  table_name: sys_db_index
  query: table=incident
  fields: name,column_name,unique_index,active
  limit: 50
ステップ 4.3:インデックスのベストプラクティス

インデックスを設定するフィールド:

フィールドタイプインデックスの優先度例
外部キー高assigned_to, caller_id
ステータスフィールド高state, active
日付フィルター中opened_at, resolved_at
カテゴリフィールド中category, subcategory
数値フィールド低number(通常は自動的にインデックス化されます)

インデックス作成のガイドライン:

  • テーブルごとに最大 5~7 個のインデックスにします(これを超えると書き込みパフォーマンスが低下します)
  • 頻繁に組み合わせて使うクエリには複合インデックスを使います
  • 頻繁に更新されるフィールドへのインデックス設定は避けます
  • 大規模なテーブルでは部分インデックスを検討します

インデックスを作成する(管理者権限が必要):

Tool: SN-Create-Record
Parameters:
  table_name: sys_db_index
  data:
    table: incident
    column_name: assignment_group
    active: true
フェーズ 5:非同期ビジネスルール
ステップ 5.1:同期処理のボトルネックを特定する

処理が遅い同期ビジネスルールを見つけます:

Tool: SN-Query-Table
Parameters:
  table_name: sys_script
  query: active=true^when!=async^collection=incident
  fields: name,when,order,sys_id
  limit: 50
ステップ 5.2:非同期に変換する

非同期への変換基準:

非同期にできるもの同期のままにする必要があるもの
通知フィールド計算
ロギング/監査データ検証
外部連携ワークフロートリガー
重要度の低い更新セキュリティチェック
レポート生成表示ビジネスルール

非同期ビジネスルールのパターン:

// Async business rule example
// When: async
// Order: 100

(function executeRule(current, previous /*null when async*/) {

  // Safe for async - doesn't affect transaction
  var event = new GlideRecord('sysevent');
  event.initialize();
  event.name = 'incident.updated';
  event.parm1 = current.sys_id.toString();
  event.parm2 = current.number.toString();
  event.insert();

  // External notification
  var restMessage = new sn_ws.RESTMessageV2('External System', 'notify');
  restMessage.setStringParameter('incident_id', current.sys_id.toString());
  restMessage.executeAsync();  // Non-blocking

})(current, previous);
ステップ 5.3:ビジネスルール最適化チェックリスト
// OPTIMIZED Business Rule Template
(function executeRule(current, previous) {

  // 1. Exit early if field hasn't changed
  if (previous && !current.state.changes()) {
    return;  // No work needed
  }

  // 2. Exit early for bulk operations
  if (current.operation() == 'update' && !gs.isInteractive()) {
    // Consider if this should run for imports/scripts
  }

  // 3. Use efficient existence checks
  if (!current.assigned_to.nil()) {
    // Only process if assigned
  }

  // 4. Cache repeated calculations
  var state = current.state.toString();  // Store once

  // 5. Avoid GlideRecord in loops
  // Use dot-walking or batch loading instead

  // 6. Set fields directly when possible
  current.work_notes = 'State changed to: ' + current.state.getDisplayValue();
  // Don't update() - let the transaction handle it

})(current, previous);
フェーズ 6:スクリプトのパフォーマンスパターン
ステップ 6.1:ループの中にループを入れない

アンチパターン:

// BAD: O(n*m) complexity
var incidents = new GlideRecord('incident');
incidents.query();
while (incidents.next()) {
  var tasks = new GlideRecord('sc_task');
  tasks.query();
  while (tasks.next()) {
    // Extremely slow: checks every task for every incident
    if (tasks.request_item == incidents.sys_id) {
      // process
    }
  }
}

最適化されたパターン:

// GOOD: O(n+m) complexity
// Build lookup map first
var tasksByRequest = {};
var tasks = new GlideRecord('sc_task');
tasks.query();
while (tasks.next()) {
  var reqId = tasks.request_item.toString();
  if (!tasksByRequest[reqId]) {
    tasksByRequest[reqId] = [];
  }
  tasksByRequest[reqId].push(tasks.sys_id.toString());
}

// Now iterate incidents with O(1) lookups
var incidents = new GlideRecord('incident');
incidents.query();
while (incidents.next()) {
  var relatedTasks = tasksByRequest[incidents.sys_id.toString()] || [];
  // process related tasks
}
ステップ 6.2:文字列の連結

文字列の繰り返し連結を避けます:

// BAD: Creates new string object each iteration
var result = '';
var gr = new GlideRecord('incident');
gr.setLimit(1000);
gr.query();
while (gr.next()) {
  result += gr.number + ', ';  // Memory inefficient
}

// GOOD: Use array join
var results = [];
var gr = new GlideRecord('incident');
gr.setLimit(1000);
gr.query();
while (gr.next()) {
  results.push(gr.number.toString());
}
var result = results.join(', ');
ステップ 6.3:効率的な JSON 処理

大きな JSON を効率的に処理します:

// BAD: Parse entire JSON into memory
var bigJson = gr.large_json_field.toString();
var data = JSON.parse(bigJson);  // Entire object in memory

// GOOD: Stream processing for large data
// Use GlideStringUtil for validation
var jsonField = gr.large_json_field.toString();
if (GlideStringUtil.isJSON(jsonField)) {
  // Process in chunks if possible
  // Or use GlideElement methods
}

// BEST: Store structured data in related records
// instead of large JSON blobs
フェーズ 7:クライアント側のパフォーマンス
ステップ 7.1:クライアントスクリプトの最適化

効率的なクライアントスクリプト:

// GOOD: Client script with performance optimizations
function onChange(control, oldValue, newValue, isLoading) {
  // 1. Skip during form load
  if (isLoading) {
    return;
  }

  // 2. Skip if value unchanged
  if (oldValue == newValue) {
    return;
  }

  // 3. Use g_form caching
  var priority = g_form.getValue('priority');  // Cached locally

  // 4. Batch field updates
  g_form.setReadOnly('resolution_code', true);
  g_form.setMandatory('resolution_notes', true);
  // Multiple setX calls are batched automatically

  // 5. Avoid synchronous GlideAjax
  var ga = new GlideAjax('IncidentUtils');
  ga.addParam('sysparm_name', 'getDefaultAssignee');
  ga.addParam('sysparm_priority', priority);
  ga.getXMLAnswer(function(answer) {
    // Async callback - doesn't block UI
    if (answer) {
      g_form.setValue('assigned_to', answer);
    }
  });
}
ステップ 7.2:UI Policy とクライアントスクリプトの比較

適切なツールを選びます:

ユースケース推奨理由
フィールドの表示/非表示UI Policy宣言的で高速
必須項目の設定UI PolicyJavaScript のオーバーヘッドがありません
単純な条件UI Policyキャッシュされ、ネットワーク通信が不要です
複雑なロジッククライアントスクリプト柔軟性が高い
GlideAjax 呼び出しクライアントスクリプトサーバーデータに必要です
フィールド計算クライアントスクリプト動的に計算できます
ステップ 7.3:フォームの読み込み時間を短縮する

フォームのパフォーマンスチェックリスト:

  • 関連リストは、デフォルトで 5 レコードに制限します
  • 表示ビジネスルールは必要な場合に限って使います
  • 可能な場合は onLoad スクリプトを避けます
  • 使用していないフォームセクションを削除します
  • 埋め込みリストは遅延読み込みします

フォームの遅いクエリを調べる:

Tool: SN-Query-Table
Parameters:
  table_name: sys_slow_transaction
  query: urlLIKEform^sys_created_on>javascript:gs.daysAgo(7)
  fields: url,response_time,user
  limit: 50
  orderByDesc: response_time
フェーズ 8:キャッシュ戦略
ステップ 8.1:システムプロパティのキャッシュ

キャッシュを利用して gs.getProperty を使います:

// Properties are cached automatically
var maxRecords = gs.getProperty('custom.max_records', '100');

// For frequently accessed properties, consider Script Include caching
var CachedConfig = Class.create();
CachedConfig.prototype = {
  initialize: function() {
    this.cache = {};
  },

  get: function(name, defaultValue) {
    if (!this.cache[name]) {
      this.cache[name] = gs.getProperty(name, defaultValue);
    }
    return this.cache[name];
  },

  type: 'CachedConfig'
};
ステップ 8.2:GlideSystem キャッシュ

組み込みのキャッシュを活用します:

// Cache expensive calculations
var cacheKey = 'user_permissions_' + gs.getUserID();
var cached = gs.getSession().getClientData(cacheKey);

if (!cached) {
  // Calculate permissions (expensive)
  var permissions = calculateUserPermissions();
  gs.getSession().putClientData(cacheKey, JSON.stringify(permissions));
  cached = JSON.stringify(permissions);
}

var userPermissions = JSON.parse(cached);
ステップ 8.3:参照修飾子のキャッシュ

参照修飾子を最適化します:

// BAD: Expensive query runs every time
javascript:new MyUtil().getValidAssignees()

// GOOD: Use encoded query directly
active=true^roles=itil

// BEST: Cache complex qualifiers
javascript:gs.getProperty('incident.assignee_qualifier', 'active=true')
フェーズ 9:パフォーマンスの監視
ステップ 9.1:パフォーマンスダッシュボードのクエリを作成する

主要なメトリクスを監視します:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Performance Health Check
    var report = {
      timestamp: new GlideDateTime().getDisplayValue(),
      metrics: {}
    };

    // 1. Slow transactions today
    var slowTx = new GlideAggregate('sys_slow_transaction');
    slowTx.addQuery('sys_created_on', '>', gs.beginningOfToday());
    slowTx.addAggregate('COUNT');
    slowTx.addAggregate('AVG', 'response_time');
    slowTx.query();
    if (slowTx.next()) {
      report.metrics.slow_transactions = {
        count: parseInt(slowTx.getAggregate('COUNT')),
        avg_response_time: Math.round(parseFloat(slowTx.getAggregate('AVG', 'response_time')))
      };
    }

    // 2. Business rule execution times
    var brLogs = new GlideAggregate('syslog_transaction');
    brLogs.addQuery('type', 'business rule');
    brLogs.addQuery('sys_created_on', '>', gs.beginningOfToday());
    brLogs.addAggregate('COUNT');
    brLogs.addAggregate('AVG', 'response_time');
    brLogs.query();
    if (brLogs.next()) {
      report.metrics.business_rules = {
        count: parseInt(brLogs.getAggregate('COUNT')),
        avg_execution_time: Math.round(parseFloat(brLogs.getAggregate('AVG', 'response_time')))
      };
    }

    // 3. Error rate
    var errors = new GlideAggregate('syslog');
    errors.addQuery('level', '2');  // Error level
    errors.addQuery('sys_created_on', '>', gs.beginningOfToday());
    errors.addAggregate('COUNT');
    errors.query();
    if (errors.next()) {
      report.metrics.errors_today = parseInt(errors.getAggregate('COUNT'));
    }

    // 4. Active scheduled jobs
    var jobs = new GlideAggregate('sys_trigger');
    jobs.addQuery('state', '0');  // Ready
    jobs.addAggregate('COUNT');
    jobs.query();
    if (jobs.next()) {
      report.metrics.pending_jobs = parseInt(jobs.getAggregate('COUNT'));
    }

    gs.info('Performance Report:\n' + JSON.stringify(report, null, 2));
  description: Generate performance health report
ステップ 9.2:アラートを設定する

クォータ違反アラートを作成します:

Tool: SN-Create-Record
Parameters:
  table_name: sysevent_script_action
  data:
    name: Performance Quota Alert
    event_name: performance.quota.exceeded
    script: |
      // Send alert on quota violations
      var quota_type = event.parm1;
      var details = event.parm2;

      gs.eventQueue('custom.performance.alert', null, quota_type, details);

ツール使用の概要

操作MCP ツール目的
低速トランザクションを照会するSN-Query-Tableパフォーマンスのボトルネックを特定する
クエリを分析するSN-Execute-Background-Script詳細な分析スクリプト
スキーマを確認するSN-Get-Table-Schemaフィールドの型を検証する
インデックスを検出するSN-Discover-Table-Schema既存のインデックスを特定する
インデックスを作成するSN-Create-Record不足しているインデックスを追加する

パフォーマンス最適化チェックリスト

GlideRecord
  • 常に setLimit() を使用する
  • setFields() または fields パラメーターで必要なフィールドのみを選択する
  • 件数の取得には GlideAggregate を使用する
  • 単一レコードの検索には get() を使用する
  • ループ内での GlideRecord の使用を避ける(N+1)
ビジネスルール
  • 条件付き実行には .changes() を使用する
  • 重要度の低いルールを非同期にする
  • ルールの順序を適切に設定する(最初に検証)
  • 可能な場合は早期に終了する
クエリ
  • 頻繁にフィルターされるフィールドにインデックスを付ける
  • 複雑な条件にはエンコードクエリを使用する
  • 大きなテキストフィールドに対する LIKE クエリを避ける
  • 日付範囲クエリの範囲を制限する
クライアント側
  • 可能な場合はクライアントスクリプトより UI ポリシーを使用する
  • GlideAjax 呼び出しを非同期にする
  • フォーム読み込み中は onChange をスキップする
  • 関連リストのレコード数を最小限にする
キャッシュ
  • 設定にはシステムプロパティを使用する
  • コストの高い計算結果をキャッシュする
  • セッションストレージを適切に活用する

トラブルシューティング

フォームの読み込み時間が長い

症状: フォームの読み込みに 5 秒以上かかる
原因:

  • 関連リストが多すぎる
  • コストの高い表示ビジネスルール
  • スクリプトを含む複雑な UI ポリシー
  • 大きな添付ファイルのプレビュー

解決策:

Tool: SN-Query-Table
Parameters:
  table_name: sys_ui_related_list
  query: table=incident
  fields: related_list,order,view
  limit: 50

関連リストを見直して数を減らし、デフォルト件数を少なく設定します。

ビジネスルールのタイムアウト

症状: トランザクションが 180 秒後にタイムアウトする
原因:

  • 上限のない GlideRecord クエリ
  • N+1 クエリパターン
  • 同期ルール内での外部 API 呼び出し

解決策:

  1. すべてのクエリに setLimit() を追加します
  2. バッチ読み込みパターンを使用します
  3. 非同期ビジネスルールに変更します
  4. インテグレーションをスケジュール済みジョブに移動します
メモリークォータ超過

症状: 「Memory quota exceeded」エラーが発生する
原因:

  • レコードを読み込みすぎている
  • 大量の文字列連結
  • 大きな JSON/XML の解析

解決策:

// Process in batches
var BATCH_SIZE = 200;
var offset = 0;
var hasMore = true;

while (hasMore) {
  var gr = new GlideRecord('incident');
  gr.addQuery('active', true);
  gr.orderBy('sys_created_on');
  gr.chooseWindow(offset, offset + BATCH_SIZE);
  gr.query();

  var count = 0;
  while (gr.next()) {
    // Process record
    count++;
  }

  hasMore = (count == BATCH_SIZE);
  offset += BATCH_SIZE;

  // Force garbage collection hint
  gs.nil();
}
リストビューの読み込みが遅い

症状: リストビューの読み込みに 7 秒以上かかる
原因:

  • フィルターフィールドにインデックスがない
  • スクリプト条件を含む複雑な ACL
  • 多数の参照フィールドでの表示値の解決

解決策:

  1. 頻繁にフィルターされる列にインデックスを追加します
  2. ACL 条件を簡素化します
  3. 表示する列を減らします
  4. パフォーマンスを向上させるために list v3 を使用します

ベストプラクティス

プラクティス効果難易度
setLimit() を追加する高簡単
setFields() を使用する高簡単
N+1 クエリを修正する重大中程度
インデックスを追加する高中程度
非同期に変更する中中程度
ループを最適化する高中程度
キャッシュを有効にする中中程度
クライアントスクリプトを非同期にする中簡単

関連スキル

  • admin/script-execution - バックグラウンドスクリプトの実行パターン
  • admin/batch-operations - 効率的な一括レコード操作
  • admin/schema-discovery - テーブル構造の理解
  • development/debugging-techniques - スクリプトのトラブルシューティング

参考資料

コメント