performance-optimization について
ServiceNowの開発者、管理者、パフォーマンスエンジニア向けの最適化スキルです。診断手順に加え、クエリ、インデックス、スクリプト、クライアント側の性能改善を扱います。
主な機能
- 01遅いトランザクションとクエリの特定
- 02GlideRecordクエリの最適化
- 03N+1データベース呼び出しの回避
- 04インデックスの確認と作成
- 05ビジネスルールとクライアントスクリプトの調整
活用シーン
- 01フォームやリストの表示遅延の調査
- 02クエリとトランザクション時間の短縮
- 03スクリプト内の重複したDB呼び出しの確認
- 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 sec | 2-5 sec | > 5 sec |
| リストビューの読み込み時間 | < 3 sec | 3-7 sec | > 7 sec |
| ビジネスルールの実行時間 | < 100 ms | 100-500 ms | > 500 ms |
| クライアントスクリプトの実行時間 | < 200 ms | 200-500 ms | > 500 ms |
| クエリ応答時間 | < 50 ms | 50-200 ms | > 200 ms |
| API 応答時間 | < 1 sec | 1-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 が必要):
- System Diagnostics > Session Debug > Debug Business Rule に移動します
- 関連するデバッガーを有効にします。
- Debug Business Rule (SQL)
- Debug Log
- Debug SQL (Detailed)
- 低速な操作を再現します
- 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 transaction | N+1 クエリパターン |
| スクリプト実行 | 180 seconds | 実行時間の長いスクリプト |
| メモリ | 100 MB | 大量のデータ処理 |
| アウトバウンド HTTP | 10 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 Policy | JavaScript のオーバーヘッドがありません |
| 単純な条件 | 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 呼び出し
解決策:
- すべてのクエリに setLimit() を追加します
- バッチ読み込みパターンを使用します
- 非同期ビジネスルールに変更します
- インテグレーションをスケジュール済みジョブに移動します
メモリークォータ超過
症状: 「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
- 多数の参照フィールドでの表示値の解決
解決策:
- 頻繁にフィルターされる列にインデックスを追加します
- ACL 条件を簡素化します
- 表示する列を減らします
- パフォーマンスを向上させるために list v3 を使用します
ベストプラクティス
| プラクティス | 効果 | 難易度 |
|---|---|---|
| setLimit() を追加する | 高 | 簡単 |
| setFields() を使用する | 高 | 簡単 |
| N+1 クエリを修正する | 重大 | 中程度 |
| インデックスを追加する | 高 | 中程度 |
| 非同期に変更する | 中 | 中程度 |
| ループを最適化する | 高 | 中程度 |
| キャッシュを有効にする | 中 | 中程度 |
| クライアントスクリプトを非同期にする | 中 | 簡単 |
関連スキル
admin/script-execution- バックグラウンドスクリプトの実行パターンadmin/batch-operations- 効率的な一括レコード操作admin/schema-discovery- テーブル構造の理解development/debugging-techniques- スクリプトのトラブルシューティング