Skillshub algolia-cost-tuning
install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/jeremylongshore/claude-code-plugins-plus-skills/algolia-cost-tuning" ~/.claude/skills/comeonoliver-skillshub-algolia-cost-tuning && rm -rf "$T"
manifest:
skills/jeremylongshore/claude-code-plugins-plus-skills/algolia-cost-tuning/SKILL.mdsource content
Algolia Cost Tuning
Overview
Algolia pricing is based on search requests and records. A search request is one API call (which may contain multiple queries via
search({ requests: [...] })). Records are counted across all indices including replicas.
Pricing Structure (2025)
| Plan | Records Included | Search Requests | Additional Cost |
|---|---|---|---|
| Build (Free) | 1M records | 10K requests/mo | N/A |
| Grow | 100K free, then $0.40/1K | 10K free, then $0.50/1K | Pay as you go |
| Grow Plus | 100K free, then $0.40/1K | 10K free, then $1.75/1K | + AI features |
| Premium | Custom | Custom | Volume discounts |
What Counts as Records
- Every object in every index = 1 record
- Standard replicas duplicate records (multiply your count)
- Virtual replicas share records (no extra cost)
- Synonyms and rules do NOT count as records
What Counts as Search Requests
= 1 requestsearchSingleIndex()
= 1 request (multi-query)search({ requests: [q1, q2, q3] })
= 1 request per pagebrowse()
= NOT a search request (indexing operations are free)saveObjects()
Instructions
Step 1: Audit Current Usage
import { algoliasearch } from 'algoliasearch'; const client = algoliasearch(process.env.ALGOLIA_APP_ID!, process.env.ALGOLIA_ADMIN_KEY!); // Check total records across all indices const { items } = await client.listIndices(); let totalRecords = 0; let replicaRecords = 0; items.forEach(idx => { const records = idx.entries || 0; console.log(`${idx.name}: ${records.toLocaleString()} records, ${(idx.dataSize || 0 / 1024).toFixed(0)}KB`); if (idx.name.includes('_replica') || idx.primary) { replicaRecords += records; } totalRecords += records; }); console.log(`\nTotal: ${totalRecords.toLocaleString()} records (${replicaRecords.toLocaleString()} in replicas)`);
Step 2: Replace Standard Replicas with Virtual Replicas
// Standard replicas: duplicate all records (doubles cost) // Virtual replicas: share records with primary (no extra cost) // BEFORE: 3 standard replicas = 4x record count await client.setSettings({ indexName: 'products', indexSettings: { replicas: [ // 'products_price_asc', // Standard: costs records // 'products_price_desc', // Standard: costs records 'virtual(products_price_asc)', // Virtual: FREE 'virtual(products_price_desc)', // Virtual: FREE ], }, }); // Virtual replica limitation: can only customize ranking and customRanking // If you need different searchableAttributes or attributesForFaceting, use standard
Step 3: Use Multi-Query to Reduce Request Count
// BAD: 3 separate requests = 3 search operations billed const results1 = await client.searchSingleIndex({ indexName: 'products', searchParams: { query: 'laptop' } }); const results2 = await client.searchSingleIndex({ indexName: 'articles', searchParams: { query: 'laptop' } }); const results3 = await client.searchSingleIndex({ indexName: 'faq', searchParams: { query: 'laptop' } }); // GOOD: 1 multi-query request = 1 search operation billed const { results } = await client.search({ requests: [ { indexName: 'products', query: 'laptop', hitsPerPage: 5 }, { indexName: 'articles', query: 'laptop', hitsPerPage: 3 }, { indexName: 'faq', query: 'laptop', hitsPerPage: 3 }, ], });
Step 4: Cache Frequent Searches
import { LRUCache } from 'lru-cache'; // Cache popular searches — Algolia's own CDN caches are limited const searchCache = new LRUCache<string, any>({ max: 1000, ttl: 5 * 60 * 1000, // 5 minutes for product search }); async function cachedSearch(query: string, filters?: string) { const key = JSON.stringify({ query, filters }); const cached = searchCache.get(key); if (cached) { console.log('Cache hit — saved 1 search request'); return cached; } const result = await client.searchSingleIndex({ indexName: 'products', searchParams: { query, filters }, }); searchCache.set(key, result); return result; }
Step 5: Delete Unused Indices
// Audit and clean up test/development indices const { items } = await client.listIndices(); const devIndices = items.filter(i => i.name.startsWith('test_') || i.name.startsWith('dev_') || i.name.startsWith('ci_') ); for (const idx of devIndices) { console.log(`Deleting unused index: ${idx.name} (${idx.entries} records)`); await client.deleteIndex({ indexName: idx.name }); }
Step 6: Monitor Usage with Analytics API
// Track search volume trends const { count: searchCount } = await client.getSearchesCount({ index: 'products', startDate: '2025-01-01', endDate: '2025-01-31', }); console.log(`Search requests this month: ${searchCount.toLocaleString()}`); // Identify no-result queries (wasted searches users will retry) const { searches } = await client.getSearchesNoResults({ index: 'products', startDate: '2025-01-01', endDate: '2025-01-31', }); console.log('Top no-result searches (fix these to reduce retries):'); searches.slice(0, 10).forEach(s => console.log(` "${s.search}" — ${s.count} times`));
Cost Reduction Summary
| Strategy | Savings | Effort |
|---|---|---|
| Virtual replicas | 50-75% record cost | Low |
| Multi-query search | 60-80% fewer requests | Low |
| Client-side caching | 30-50% fewer requests | Low |
| Delete unused indices | Variable | Low |
| Fix no-result queries (synonyms) | 10-20% fewer retries | Medium |
| Reduce record size | Indirect (faster = cheaper) | Medium |
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Unexpected bill spike | Uncached bot traffic | Add rate limiting, cache layer |
| Record count higher than expected | Standard replicas | Switch to virtual replicas |
| Search requests over budget | No caching | Add LRU cache in API layer |
| Analytics API returns empty | Wrong date range or region | Check parameter matches your app |
Resources
Next Steps
For architecture patterns, see
algolia-reference-architecture.