はじめに
前回は関連記事として、Firebase Genkitを活用して、Function Callingから自己学習ループまでを備えたパーソナルAIエージェントのバックエンドを構築しました。
このAIエージェントの「長期記憶」を支えるコア技術として、Vertex AI Vector Searchを利用したハイブリッド検索を採用しています。
しかし、開発フェーズや社内PoCの段階において、「Vector Searchのインデックスを24時間稼働させ続けるとランニングコストが高くついてしまう」 というインフラ運用上の課題に直面しました。
そこで今回は、Firebase GenkitのフローとCloud Schedulerを組み合わせ、「利用しない夜間や休日はインデックスをアンデプロイし、業務開始前に自動でデプロイするバッチ処理」 を実装し、コストを大幅に最適化する方法とその知見をまとめます。
対象読者
- Firebase Genkitを使った実践的な運用・インフラ管理に関心がある方
- Vertex AI Vector Searchを利用中で、開発・検証環境のコスト削減を図りたい方
- Firebase Cloud Functions (v2) のスケジュール実行 (onSchedule) の実装例を知りたい方
課題:Vector Searchの待機コスト
Vertex AI Vector Searchは、数百万〜数十億スケールの高次元ベクトルをミリ秒単位で検索できる非常に強力なサービスですが、検索クエリを受け付けるためには「インデックスをエンドポイントにデプロイ」した状態にしておく必要があります。
この「デプロイされたインデックス」は、検索リクエストの有無にかかわらず、稼働している時間(ノードの時間)に対して課金が発生 します。
24時間365日フル稼働させた場合、社内検証用の小規模なノードであっても、月額で数万円単位のコストがかかってしまいます。
「平日の日中(業務時間中)しかテストを行わない」のであれば、夜間や休日はアンデプロイしておくことで、ランニングコストを半分以下に抑えることが可能です。
解決策:Genkit Flow × Cloud Schedulerによる自動化
この起動・停止のオペレーションを手動で行うのは現実的ではないため、Firebase Genkit の機能を使って自動化します。
アーキテクチャとしては非常にシンプルで、以下の2つの要素で構成されます。
- Genkit Flow: @google-cloud/aiplatform SDKを使用して、実際にデプロイ/アンデプロイのAPIを叩く処理
- Cloud Functions (onSchedule): 指定した時間(cron)に上記のFlowを呼び出すトリガー
実装
実際に稼働しているバックエンドのコードをベースに解説します。
1. デプロイ / アンデプロイを実行するGenkit Flow
まず、Vector Searchを操作するためのフローを定義します。
(抜粋)
import { z } from 'genkit';
import { ai } from '../core/bootstrap';
import { IndexEndpointServiceClient } from '@google-cloud/aiplatform';
import { PROJECT_ID, VECTOR_SEARCH_LOCATION, PUBLIC_ENDPOINT_ID, INDEX_ID, PUBLIC_DEPLOYED_ID } from '../constants/config';
/**
* Vector Searchインデックスをデプロイするフロー
*/
export const deployVectorIndexFlow = ai.defineFlow(
{ name: 'deployVectorIndexFlow', inputSchema: z.void(), outputSchema: z.void() },
async () => {
console.log('[Vector Deploy] Starting Vertex AI Vector Search index deployment...');
try {
const client = new IndexEndpointServiceClient({
apiEndpoint: `${VECTOR_SEARCH_LOCATION}-aiplatform.googleapis.com`,
});
const indexEndpoint = `projects/${PROJECT_ID}/locations/${VECTOR_SEARCH_LOCATION}/indexEndpoints/${PUBLIC_ENDPOINT_ID}`;
const index = `projects/${PROJECT_ID}/locations/${VECTOR_SEARCH_LOCATION}/indexes/${INDEX_ID}`;
const request = {
indexEndpoint: indexEndpoint,
deployedIndex: {
id: PUBLIC_DEPLOYED_ID,
displayName: PUBLIC_DEPLOYED_ID,
index: index,
dedicatedResources: {
machineSpec: {
machineType: 'e2-standard-2', // 用途に合わせてマシンスペックを指定
},
minReplicaCount: 1,
maxReplicaCount: 1,
},
},
};
const [operation] = await client.deployIndex(request);
console.log('[Vector Deploy] Deploy operation started. It will take 20-30 minutes to complete.');
} catch (error: any) {
// 既にデプロイ済みの場合はエラーを無視して処理を続行
if (error.message && error.message.includes('is already deployed')) {
console.log('[Vector Deploy] Index is already deployed. No action needed.');
} else {
console.error('[Vector Deploy] Failed to start index deployment:', error);
}
}
}
);
/**
* Vector Searchインデックスをアンデプロイするフロー
*/
export const undeployVectorIndexFlow = ai.defineFlow(
{ name: 'undeployVectorIndexFlow', inputSchema: z.void(), outputSchema: z.void() },
async () => {
console.log('[Vector Undeploy] Starting Vertex AI Vector Search index undeployment...');
try {
const client = new IndexEndpointServiceClient({
apiEndpoint: `${VECTOR_SEARCH_LOCATION}-aiplatform.googleapis.com`,
});
const request = {
indexEndpoint: `projects/${PROJECT_ID}/locations/${VECTOR_SEARCH_LOCATION}/indexEndpoints/${PUBLIC_ENDPOINT_ID}`,
deployedIndexId: PUBLIC_DEPLOYED_ID,
};
const [operation] = await client.undeployIndex(request);
console.log('[Vector Undeploy] Undeploy operation started.');
} catch (error: any) {
console.error('[Vector Undeploy] Failed to start index undeployment:', error);
}
}
);
実装のポイント:
- GCPの IndexEndpointServiceClient を利用してAPIを呼び出しています
- デプロイ処理(deployIndex)は 完了までに20〜30分程度かかる 非同期オペレーションですが、ここでは「処理の開始(リクエストの送信)」のみを待機して関数を終了させています
- 何らかの理由で既にデプロイされている状態(is already deployed)でフローが実行されても、エラーでクラッシュしないようにハンドリングしています
2. Firebase Functionsでのスケジュール設定
作成したFlowを、指定した時間に自動実行させるために onSchedule を使ってエクスポートします。
(抜粋)
import { onSchedule } from 'firebase-functions/v2/scheduler';
import { deployVectorIndexFlow, undeployVectorIndexFlow } from './flows';
// Vector Searchをデプロイ(平日朝9時に実行)
export const deployVectorIndex = onSchedule({
region: 'asia-northeast1',
schedule: "every mon,tue,wed,thu,fri 09:00",
memory: '1GiB',
timeZone: 'Asia/Tokyo'
}, async (event) => {
await deployVectorIndexFlow.run();
});
// Vector Searchをアンデプロイ(平日夜18時に実行)
export const undeployVectorIndex = onSchedule({
region: 'asia-northeast1',
schedule: "every mon,tue,wed,thu,fri 18:00",
memory: '1GiB',
timeZone: 'Asia/Tokyo'
}, async (event) => {
await undeployVectorIndexFlow.run();
});
スケジュールのポイント:
- schedule にcron形式(every mon,tue,wed,thu,fri 09:00)で稼働させたい時間を指定します
- 前述の通り、Vector Searchのデプロイには20〜30分程度の時間がかかります
- そのため、業務開始時間(例:9:30)に合わせてデプロイするのではなく、十分な余裕を持って(例:9:00)にスケジュールを設定しておくのがベストプラクティスです
デプロイと運用確認
上記の設定ができたら、Firebaseにデプロイします。
firebase deploy --only functions
デプロイ後、GCPの「Cloud Scheduler」のコンソールを確認すると、指定したタイムゾーン(Asia/Tokyo)でジョブが登録されていることが確認できます。
※上記のキャプチャでは、 デプロイ6:00実行 / アンデプロイ23:00実行 としています。
おわりに
今回は、Firebase GenkitとCloud Schedulerを活用し、Vertex AI Vector Searchの稼働時間をコントロールする自動化バッチの実装について紹介しました。
このような運用スクリプトも、メインのAI機能(LLMの呼び出し等)と同じ「Genkit Flow」として一元管理することで、コードの統一性が保たれ、ローカルのDeveloper UI (genkit start) から手軽にテスト実行できるという大きなメリットがあります。
