agentsclimarketplace

Algolia performance tuning

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/algolia-pack/skills/algolia-performance-tuning

425 plugins, 2,810 skills, 200 agents for Claude Code. Open-source marketplace at tonsofskills.com with the ccpi CLI package manager.

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill algolia-performance-tuning

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

'Optimize Algolia search performance: record size, searchable attributes,

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

7.1 KB, as published. Nobody here has run it

Algolia Performance Tuning

Overview

Algolia's edge infrastructure typically delivers search in < 50ms globally. When performance degrades, the causes are usually: oversized records, too many searchable attributes, unoptimized faceting, or missing client-side caching. This skill covers server-side and client-side optimizations.

Performance Baselines

MetricGoodWarningAction Needed
Search latency (P50)< 20ms20-100ms> 100ms
Search latency (P95)< 50ms50-200ms> 200ms
Indexing time per 1K records< 2s2-10s> 10s
Record size (avg)< 5KB5-50KB> 50KB

Instructions

Step 1: Optimize Record Size

import { algoliasearch } from 'algoliasearch';

const client = algoliasearch(process.env.ALGOLIA_APP_ID!, process.env.ALGOLIA_ADMIN_KEY!);

// BAD: Full record with unnecessary data
const badRecord = {
  objectID: '1',
  name: 'Running Shoes',
  full_html_description: '<div>...5000 chars of HTML...</div>',  // Too big
  internal_notes: 'Supplier ref: ABC-123',                       // Not searchable
  all_reviews: [/* 200 reviews */],                                // Huge array
};

// GOOD: Lean record for search
const goodRecord = {
  objectID: '1',
  name: 'Running Shoes',
  description: 'Lightweight running shoes with cushioned sole',  // Plain text, truncated
  category: 'shoes',
  brand: 'Nike',
  price: 129.99,
  rating: 4.5,
  review_count: 200,          // Count, not full reviews
  in_stock: true,
  image_url: '/images/1.jpg', // URL, not base64
};

Step 2: Optimize Searchable Attributes

await client.setSettings({
  indexName: 'products',
  indexSettings: {
    // Order matters: first attribute = highest priority in ranking
    // Fewer searchable attributes = faster search
    searchableAttributes: [
      'name',                    // Highest priority
      'brand',
      'category',
      'unordered(description)',  // unordered = position in attribute doesn't affect ranking
    ],
    // DON'T make IDs, URLs, or numeric fields searchable

    // unretrievableAttributes: fields searchable but never returned in hits
    // Use for fields users should match against but not see
    unretrievableAttributes: ['internal_tags'],

    // attributesToRetrieve: limit what comes back (smaller response = faster)
    attributesToRetrieve: ['name', 'brand', 'price', 'image_url', 'category'],
  },
});

Step 3: Optimize Faceting

await client.setSettings({
  indexName: 'products',
  indexSettings: {
    attributesForFaceting: [
      'category',              // Regular facet: counts computed
      'brand',                 // Regular facet
      'filterOnly(price)',     // filterOnly: no counts = faster
      'filterOnly(in_stock)',  // Use for boolean/numeric filters
      'filterOnly(created_at)',
    ],
    // filterOnly() saves CPU — use it when you don't need facet counts
    // searchable(brand) lets users search within facet values
  },
});

Step 4: Client-Side Response Caching

import { LRUCache } from 'lru-cache';

const searchCache = new LRUCache<string, any>({
  max: 500,           // Max cached queries
  ttl: 60 * 1000,     // 1 minute TTL
});

async function cachedSearch(query: string, filters?: string) {
  const cacheKey = `${query}|${filters || ''}`;
  const cached = searchCache.get(cacheKey);
  if (cached) return cached;

  const result = await client.searchSingleIndex({
    indexName: 'products',
    searchParams: { query, filters, hitsPerPage: 20 },
  });

  searchCache.set(cacheKey, result);
  return result;
}

Step 5: Query-Time Optimization Parameters

const { hits } = await client.searchSingleIndex({
  indexName: 'products',
  searchParams: {
    query: 'laptop',

    // Reduce response size
    attributesToRetrieve: ['name', 'price', 'image_url'],  // Only what UI needs
    attributesToHighlight: ['name'],                         // Fewer = faster
    attributesToSnippet: [],                                 // Skip snippets if not used
    responseFields: ['hits', 'nbHits', 'page', 'nbPages'],  // Skip unnecessary metadata

    // Limit processing
    hitsPerPage: 20,              // Don't over-fetch
    maxValuesPerFacet: 10,        // Limit facet values returned

    // Disable features you don't use
    // typoTolerance: false,      // Uncomment if exact matching is fine
    // removeStopWords: false,    // Keep stop words in query
  },
});

Step 6: Replica Strategy for Sort Orders

// Standard replicas share data but have their own ranking
// Virtual replicas share data AND ranking config (less storage cost)
await client.setSettings({
  indexName: 'products',
  indexSettings: {
    replicas: [
      'virtual(products_price_asc)',   // Virtual: cheaper, limited customization
      'virtual(products_price_desc)',
      'products_newest',               // Standard: full ranking control
    ],
  },
});

// Virtual replica can only override: customRanking and ranking
// Standard replica can override all settings

Performance Monitoring

async function measureSearchLatency(query: string, iterations = 10) {
  const latencies: number[] = [];

  for (let i = 0; i < iterations; i++) {
    const start = performance.now();
    await client.searchSingleIndex({
      indexName: 'products',
      searchParams: { query, hitsPerPage: 20 },
    });
    latencies.push(performance.now() - start);
  }

  latencies.sort((a, b) => a - b);
  console.log({
    p50: latencies[Math.floor(iterations * 0.5)].toFixed(1),
    p95: latencies[Math.floor(iterations * 0.95)].toFixed(1),
    p99: latencies[Math.floor(iterations * 0.99)].toFixed(1),
    avg: (latencies.reduce((a, b) => a + b) / iterations).toFixed(1),
  });
}

Error Handling

IssueCauseSolution
P95 > 200msOversized recordsTrim records, use unretrievableAttributes
Facet queries slowToo many facet valuesUse filterOnly() or maxValuesPerFacet
Indexing slowLarge batch + complex settingsReduce batch size, simplify searchableAttributes
Cache stampedeTTL expired, burst trafficUse stale-while-revalidate pattern

Resources

Next Steps

For cost optimization, see algolia-cost-tuning.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.