Claude-code-plugins-plus salesforce-performance-tuning
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/salesforce-pack/skills/salesforce-performance-tuning" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-salesforce-performance-tuning && rm -rf "$T"
manifest:
plugins/saas-packs/salesforce-pack/skills/salesforce-performance-tuning/SKILL.mdsource content
Salesforce Performance Tuning
Overview
Optimize Salesforce API performance: tune SOQL queries, minimize API calls using Composite/Collections APIs, implement metadata caching, and handle large result sets efficiently.
Prerequisites
- jsforce connection configured
- Understanding of SOQL query plans
- Redis or in-memory cache available (optional)
- Access to Setup > API usage monitoring
Instructions
Step 1: SOQL Query Optimization
// BAD: SELECT * equivalent — fetches all fields const result = await conn.query('SELECT FIELDS(ALL) FROM Account LIMIT 100'); // GOOD: Only select fields you need const result = await conn.query(` SELECT Id, Name, Industry, AnnualRevenue FROM Account WHERE Industry = 'Technology' LIMIT 100 `); // BAD: Non-selective WHERE clause (full table scan) const result = await conn.query("SELECT Id FROM Contact WHERE Title LIKE '%Engineer%'"); // GOOD: Use indexed fields in WHERE (Id, Name, CreatedDate, RecordType, lookup fields) const result = await conn.query(` SELECT Id, Name, Title FROM Contact WHERE AccountId = '001xxxxxxxxxxxx' AND CreatedDate >= LAST_N_DAYS:30 LIMIT 200 `); // Use relationship queries to avoid N+1 pattern // BAD: Query Accounts, then query Contacts for each (N+1 API calls) const accounts = await conn.query('SELECT Id FROM Account LIMIT 50'); for (const acct of accounts.records) { await conn.query(`SELECT Id FROM Contact WHERE AccountId = '${acct.Id}'`); // 50 extra API calls! } // GOOD: Single relationship query (1 API call) const accountsWithContacts = await conn.query(` SELECT Id, Name, (SELECT Id, FirstName, LastName, Email FROM Contacts LIMIT 20) FROM Account WHERE Industry = 'Technology' LIMIT 50 `);
Step 2: Reduce API Call Count
// STRATEGY 1: sObject Collections — 200 records per API call // Instead of 100 individual creates = 100 API calls const contacts = Array.from({ length: 100 }, (_, i) => ({ FirstName: `User${i}`, LastName: `Test`, Email: `user${i}@test.com`, })); await conn.sobject('Contact').create(contacts); // 1 API call // STRATEGY 2: Composite API — 25 mixed operations per API call // Create Account + Contact + Opportunity = 1 API call instead of 3 // See salesforce-core-workflow-b // STRATEGY 3: queryMore for pagination — FREE (doesn't count as extra call) let result = await conn.query('SELECT Id, Name FROM Contact'); let allRecords = [...result.records]; while (!result.done) { result = await conn.queryMore(result.nextRecordsUrl!); allRecords.push(...result.records); }
Step 3: Cache Metadata (Describe Calls)
import { LRUCache } from 'lru-cache'; // Describe calls are expensive and metadata rarely changes const describeCache = new LRUCache<string, any>({ max: 50, // Cache up to 50 sObject describes ttl: 1000 * 60 * 60, // 1 hour TTL (metadata changes are rare) }); async function cachedDescribe(sObjectType: string) { const cached = describeCache.get(sObjectType); if (cached) return cached; const conn = await getConnection(); const describe = await conn.sobject(sObjectType).describe(); describeCache.set(sObjectType, describe); return describe; } // Cache SOQL query results for frequently-accessed reference data const queryCache = new LRUCache<string, any>({ max: 100, ttl: 1000 * 60 * 5, // 5 minute TTL for query results }); async function cachedQuery<T>(soql: string): Promise<T[]> { const cached = queryCache.get(soql); if (cached) return cached; const conn = await getConnection(); const result = await conn.query<T>(soql); queryCache.set(soql, result.records); return result.records; }
Step 4: Stream Large Result Sets
// For large exports (100K+ records), use Bulk API 2.0 query // Streams results to avoid loading everything into memory const queryResults = await conn.bulk2.query( 'SELECT Id, Name, Email FROM Contact WHERE CreatedDate >= LAST_YEAR' ); // Process as async iterator — constant memory usage let count = 0; for await (const record of queryResults) { await processContact(record); count++; if (count % 10000 === 0) { console.log(`Processed ${count} records...`); } }
Step 5: Connection Optimization
// Reuse connections across requests (singleton pattern) // jsforce handles keep-alive internally // Pin API version to avoid version negotiation overhead const conn = new jsforce.Connection({ loginUrl: process.env.SF_LOGIN_URL, version: '59.0', // Skip version detection call maxRequest: 10, // Max concurrent requests });
Performance Benchmarks
| Operation | Typical Latency | Optimization |
|---|---|---|
| Single SOQL query | 100-300ms | Use selective filters on indexed fields |
| sObject Create (single) | 150-400ms | Batch with Collections (up to 200) |
| Describe call | 200-500ms | Cache for 1 hour |
| Bulk API job creation | 500ms-2s | Use for 10K+ records |
| Composite (25 subrequests) | 500ms-3s | Replaces 25 individual calls |
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| WHERE clause too broad | Add indexed field filters |
| Too many joins/subqueries | Simplify or split into multiple queries |
| Too many results | Add LIMIT, or use Bulk API for exports |
| Cache stampede | TTL expired, all threads miss | Use stale-while-revalidate pattern |
Resources
Next Steps
For cost optimization, see
salesforce-cost-tuning.