Claude-code-plugins-plus apollo-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/apollo-pack/skills/apollo-performance-tuning" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-apollo-performance-tuning && rm -rf "$T"
manifest:
plugins/saas-packs/apollo-pack/skills/apollo-performance-tuning/SKILL.mdsource content
Apollo Performance Tuning
Overview
Optimize Apollo.io API performance through response caching, connection pooling, bulk operations, parallel fetching, and result slimming. Key insight: search is free but slow (~500ms), enrichment costs credits — cache aggressively and batch enrichment calls.
Prerequisites
- Valid Apollo API key
- Node.js 18+
Instructions
Step 1: Connection Pooling
Reuse TCP connections to avoid TLS handshake overhead on every request.
// src/apollo/optimized-client.ts import axios from 'axios'; import https from 'https'; const httpsAgent = new https.Agent({ keepAlive: true, maxSockets: 10, maxFreeSockets: 5, timeout: 30_000, }); export const optimizedClient = axios.create({ baseURL: 'https://api.apollo.io/api/v1', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.APOLLO_API_KEY! }, httpsAgent, timeout: 15_000, });
Step 2: Response Caching with Per-Endpoint TTLs
// src/apollo/cache.ts import { LRUCache } from 'lru-cache'; // Different TTLs based on data volatility const CACHE_TTLS: Record<string, number> = { '/organizations/enrich': 24 * 60 * 60 * 1000, // 24h — company data rarely changes '/people/match': 4 * 60 * 60 * 1000, // 4h — contact data changes occasionally '/mixed_people/api_search': 15 * 60 * 1000, // 15min — search results are dynamic '/mixed_companies/search': 30 * 60 * 1000, // 30min — company search '/contact_stages': 60 * 60 * 1000, // 1h — stages rarely change }; const cache = new LRUCache<string, { data: any; at: number }>({ max: 5000, maxSize: 50 * 1024 * 1024, sizeCalculation: (v) => JSON.stringify(v).length, }); function cacheKey(endpoint: string, params: any): string { return `${endpoint}:${JSON.stringify(params)}`; } export async function cachedRequest<T>( endpoint: string, requestFn: () => Promise<T>, params: any, ): Promise<T> { const key = cacheKey(endpoint, params); const ttl = CACHE_TTLS[endpoint] ?? 15 * 60 * 1000; const cached = cache.get(key); if (cached && Date.now() - cached.at < ttl) return cached.data; const data = await requestFn(); cache.set(key, { data, at: Date.now() }); return data; } export function getCacheStats() { return { entries: cache.size, sizeBytes: cache.calculatedSize }; }
Step 3: Use Bulk Endpoints Over Single Calls
Apollo's bulk enrichment endpoint handles 10 records per call vs 1. Massive performance gain.
// src/apollo/bulk-ops.ts import { optimizedClient } from './optimized-client'; import PQueue from 'p-queue'; const queue = new PQueue({ concurrency: 3, intervalCap: 2, interval: 1000 }); // Enrich 100 people: 100 individual calls = 100 requests @ 500ms = 50s // Batch of 10: 10 bulk calls @ 600ms = 6s (8x faster, same credits) export async function batchEnrich( details: Array<{ email?: string; linkedin_url?: string; first_name?: string; last_name?: string; organization_domain?: string }>, ): Promise<any[]> { const results: any[] = []; for (let i = 0; i < details.length; i += 10) { const batch = details.slice(i, i + 10); const result = await queue.add(async () => { const { data } = await optimizedClient.post('/people/bulk_match', { details: batch, reveal_personal_emails: false, reveal_phone_number: false, }); return data.matches ?? []; }); results.push(...(result ?? [])); } return results; }
Step 4: Parallel Search with Concurrency Control
export async function parallelSearch( domains: string[], concurrency: number = 5, ): Promise<Map<string, any[]>> { const searchQueue = new PQueue({ concurrency }); const results = new Map<string, any[]>(); await searchQueue.addAll( domains.map((domain) => async () => { const data = await cachedRequest( '/mixed_people/api_search', () => optimizedClient.post('/mixed_people/api_search', { q_organization_domains_list: [domain], person_seniorities: ['vp', 'director', 'c_suite'], per_page: 25, }).then((r) => r.data), { domain }, ); results.set(domain, data.people ?? []); }), ); return results; }
Step 5: Slim Response Payloads
Apollo returns large person objects (~2KB each). Extract only needed fields to reduce memory.
interface SlimPerson { id: string; name: string; title: string; email?: string; company: string; seniority: string; } function slimPerson(raw: any): SlimPerson { return { id: raw.id, name: raw.name, title: raw.title, email: raw.email, company: raw.organization?.name ?? '', seniority: raw.seniority ?? '', }; } // Use immediately after API call to free memory const { data } = await optimizedClient.post('/mixed_people/api_search', { ... }); const slim = data.people.map(slimPerson); // ~200 bytes each instead of ~2KB
Step 6: Benchmark Your Endpoints
async function benchmark() { const endpoints = [ { name: 'People Search', fn: () => optimizedClient.post('/mixed_people/api_search', { q_organization_domains_list: ['apollo.io'], per_page: 1 }) }, { name: 'Org Enrich', fn: () => optimizedClient.get('/organizations/enrich', { params: { domain: 'apollo.io' } }) }, { name: 'Auth Health', fn: () => optimizedClient.get('/auth/health') }, ]; for (const ep of endpoints) { const times: number[] = []; for (let i = 0; i < 5; i++) { const start = Date.now(); try { await ep.fn(); } catch {} times.push(Date.now() - start); } const avg = Math.round(times.reduce((a, b) => a + b) / times.length); const p95 = times.sort((a, b) => a - b)[Math.floor(times.length * 0.95)]; console.log(`${ep.name}: avg=${avg}ms, p95=${p95}ms`); } }
Output
- Connection pooling with
and configurablekeepAlivemaxSockets - LRU cache with per-endpoint TTLs (24h org, 4h contact, 15m search)
- Bulk enrichment via
(10x fewer requests)/people/bulk_match - Parallel search with
concurrency controlp-queue - Response slimming reducing memory from ~2KB to ~200B per person
- Benchmarking script measuring avg and p95 latency
Error Handling
| Issue | Resolution |
|---|---|
| High latency | Enable connection pooling, check for stale cache |
| Cache misses | Increase TTL for stable data (org enrichment) |
| Rate limits with parallelism | Reduce p-queue concurrency |
| Memory growth | Lower LRU max entries, slim response payloads |
Resources
Next Steps
Proceed to
apollo-cost-tuning for cost optimization.