algolia-cost-tuning
Algoliaのコスト最適化:検索リクエストとレコード課金の違いを理解し、バッチ処理とキャッシングで操作を削減し、Analytics APIで利用状況を監視します。 トリガーキーワード:「algolia cost」「algolia billing」「reduce algolia costs」「algolia pricing」「algolia expensive」「algolia budget」
description の原文を見る
Optimize Algolia costs: understand search request vs record pricing, reduce operations with batching and caching, monitor usage via Analytics API. Trigger: "algolia cost", "algolia billing", "reduce algolia costs", "algolia pricing", "algolia expensive", "algolia budget".
SKILL.md 本文
Algolia コスト最適化
概要
Algoliaの価格設定は検索リクエストとレコードに基づいています。検索リクエストは1つのAPI呼び出しです(search({ requests: [...] })で複数のクエリが含まれることもあります)。レコードはレプリカを含むすべてのインデックスで集計されます。
価格体系 (2025)
| プラン | 含まれるレコード | 検索リクエスト | 追加費用 |
|---|---|---|---|
| Build (無料) | 100万レコード | 月1万リクエスト | なし |
| Grow | 10万無料、その後$0.40/1千 | 月1万無料、その後$0.50/1千 | 従量課金 |
| Grow Plus | 10万無料、その後$0.40/1千 | 月1万無料、その後$1.75/1千 | + AI機能 |
| Premium | カスタム | カスタム | ボリュームディスカウント |
レコードとして計算される対象
- すべてのインデックス内の各オブジェクト = 1レコード
- 標準レプリカはレコードを複製します(カウント数が増加)
- 仮想レプリカはレコードを共有します(追加コストなし)
- シノニムとルールはレコードとしてカウントされません
検索リクエストとして計算される対象
searchSingleIndex()= 1リクエストsearch({ requests: [q1, q2, q3] })= 1リクエスト (マルチクエリ)browse()= ページあたり1リクエストsaveObjects()= 検索リクエストではありません (インデックス作成操作は無料)
手順
ステップ1: 現在の利用状況を監査
import { algoliasearch } from 'algoliasearch';
const client = algoliasearch(process.env.ALGOLIA_APP_ID!, process.env.ALGOLIA_ADMIN_KEY!);
// すべてのインデックスのレコード総数を確認
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)`);
ステップ2: 標準レプリカを仮想レプリカに置き換える
// 標準レプリカ: すべてのレコードを複製 (コスト2倍)
// 仮想レプリカ: プライマリインデックスと共有 (追加コストなし)
// 変更前: 3つの標準レプリカ = レコードカウント4倍
await client.setSettings({
indexName: 'products',
indexSettings: {
replicas: [
// 'products_price_asc', // 標準: レコードコスト発生
// 'products_price_desc', // 標準: レコードコスト発生
'virtual(products_price_asc)', // 仮想: 無料
'virtual(products_price_desc)', // 仮想: 無料
],
},
});
// 仮想レプリカの制限: ランキングとcustomRankingのカスタマイズのみ可能
// searchableAttributesやattributesForFacetingを別に変更する必要がある場合は標準レプリカを使用
ステップ3: マルチクエリを使用してリクエスト数を削減
// 悪い例: 3つの別々のリクエスト = 3つの検索操作に課金
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' } });
// 良い例: 1つのマルチクエリリクエスト = 1つの検索操作に課金
const { results } = await client.search({
requests: [
{ indexName: 'products', query: 'laptop', hitsPerPage: 5 },
{ indexName: 'articles', query: 'laptop', hitsPerPage: 3 },
{ indexName: 'faq', query: 'laptop', hitsPerPage: 3 },
],
});
ステップ4: 頻繁な検索をキャッシュ
import { LRUCache } from 'lru-cache';
// 人気のある検索をキャッシュ — Algoliaの独自CDNキャッシュは制限あり
const searchCache = new LRUCache<string, any>({
max: 1000,
ttl: 5 * 60 * 1000, // 製品検索用5分
});
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;
}
ステップ5: 未使用のインデックスを削除
// テスト/開発用インデックスを監査してクリーンアップ
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 });
}
ステップ6: Analytics APIを使用して利用状況を監視
// 検索ボリュームのトレンドを追跡
const { count: searchCount } = await client.getSearchesCount({
index: 'products',
startDate: '2025-01-01',
endDate: '2025-01-31',
});
console.log(`Search requests this month: ${searchCount.toLocaleString()}`);
// 結果なしのクエリを特定 (ユーザーが再試行する無駄な検索)
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`));
コスト削減の概要
| 戦略 | 削減効果 | 実装難度 |
|---|---|---|
| 仮想レプリカ | レコードコスト50-75%削減 | 低 |
| マルチクエリ検索 | リクエスト60-80%削減 | 低 |
| クライアント側キャッシング | リクエスト30-50%削減 | 低 |
| 未使用インデックスの削除 | 可変 | 低 |
| 結果なしクエリの修正 (シノニム) | 再試行10-20%削減 | 中 |
| レコードサイズを削減 | 間接的 (高速化=安価化) | 中 |
エラーハンドリング
| 問題 | 原因 | 解決策 |
|---|---|---|
| 予期しない請求額の増加 | キャッシュされていないボット トラフィック | レート制限またはキャッシュ層を追加 |
| 予想より高いレコード数 | 標準レプリカ | 仮想レプリカに切り替え |
| 予算を超える検索リクエスト | キャッシングなし | API層にLRUキャッシュを追加 |
| Analytics APIが空を返す | 日付範囲またはリージョンが誤り | regionパラメータがアプリに一致しているか確認 |
リソース
次のステップ
アーキテクチャパターンについては、algolia-reference-architectureを参照してください。
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- ComeOnOliver
- ライセンス
- MIT
- 最終更新
- 2026/5/11
Source: https://github.com/ComeOnOliver/skillshub / ライセンス: MIT