次世代 OpenSearch Serverless で「自分のブログ」を意味検索できるようにしてみた
こんにちは~ 浮田です。
■ はじめに
前回の記事で Amplify AI Kit のチャットアプリを作ったとき、ブログ記事と実装メモがそれなりの分量になりました。そして思ったんです。
「この自分のメモ、あとで意味で検索できたら便利じゃない?」と。
ちょうど 2026年5月、Amazon OpenSearch Serverless の次世代版(NextGen) が一般提供されました。「使ってないのに料金が発生する」という旧版の不満が、ゼロから作り直されて解消されたらしい。
**アイドル時は本当に0円になる(Scale-to-Zero)**という触れ込みです。
本当に0円になるのか? そして、自分のブログを放り込んで「意味で検索」できるのか?
実際に触って確かめた記録です。
■ 何が変わったのか(次世代版)
旧版との一番の違いは、コンピュートとストレージが完全に分離されたこと。新しい共有ストレージ層の上で、計算リソースだけを独立して増減できるようになりました。
その結果:
- Scale-to-Zero:リクエストが来ない状態が続くと、計算リソース(OCU)が0になる。再開時は約10秒で復帰
- 20倍速いスケール、リソース作成は秒単位
- ピーク前提でプロビジョニングするより最大60%のコスト削減
OCU(OpenSearch Compute Unit)という課金単位は旧版と同じですが、旧版は最低2OCUが常時動きっぱなし(=使わなくても月数百ドル級)だったのが、次世代版 はアイドルで0まで落ちる。
ここが決定的に違います。
公式ドキュメント
■ なぜ「エージェント向け」と言われるのか
次世代版 は「エージェント(AI)向けに作り直した」とアピールされています。AIエージェントの検索って、人間と違って不規則でバースト的なんですよね。普段は静か、必要なときに一気に検索が飛んでくる。
旧版の「常時最低2OCU」モデルは、この使い方だと「待機時間の課金」が無駄になる。
次世代版 の「普段は0、来たら一気に立ち上がる」は、まさにこの用途に経済的に噛み合う、という理屈です。
コレクションの種類は search(全文検索) と vector(ベクトル検索) の2つ。
今回は意味検索をやりたいので vector を使います。
■ 今回の題材:自分のブログ
検索対象は 前回の記事+実装メモ(自分の文章)にしました。
理由は3つ:
- 実用的:過去の自分のメモに「意味」で聞ける
- 著作権がクリア:全部自分が書いたもの
- 「ブログを書く(第1弾)→ そのブログを意味検索できるようにする(第2弾)→ そのブログに質問できるAIにする(第3弾)」と、シリーズが一本につながります。
■ やってみた:コレクション作成(Express Create)
次世代版 はコンソールの Express Create が圧倒的に楽でした。コレクション名(blog-search)とタイプ(Vector search)を選んで作成するだけ。
暗号化・ネットワーク・データアクセスのポリシーを全部自動で作ってくれて、数分で Active になります。
作成後に気づいたんですが、nextgen-blog-search という collection group が自動で作られていました。
次世代版 は、この collection group という単位で計算容量(OCU)を共有管理する仕組みになっています。後で出てくるコスト計測も、この collection group 単位で見ます。
エンドポイントはこんな形式(旧版の .aoss.amazonaws.com ではなく .on.aws):
https://<コレクションID>.aoss.ap-northeast-1.on.aws
%E3%81%A8%E3%82%BF%E3%82%A4%E3%83%97(Vector%20search)%E3%82%92%E9%81%B8%E3%82%93%E3%81%A7%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B%E3%81%A0%E3%81%91%E3%80%82%E6%9A%97%E5%8F%B7%E5%8C%96%E3%83%BB%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF%E3%83%BB%E3%83%87%E3%83%BC%E3%82%BF%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%81%AE%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC%E3%82%92%E5%85%A8%E9%83%A8%E8%87%AA%E5%8B%95%E3%81%A7%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%8F%E3%82%8C%E3%81%A6%E3%80%81%E6%95%B0%E5%88%86%E3%81%A7%20Active%20%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82.png?width=1539&height=698&name=%E6%AC%A1%E4%B8%96%E4%BB%A3%E7%89%88%20%E3%81%AF%E3%82%B3%E3%83%B3%E3%82%BD%E3%83%BC%E3%83%AB%E3%81%AE%20Express%20Create%20%E3%81%8C%E5%9C%A7%E5%80%92%E7%9A%84%E3%81%AB%E6%A5%BD%E3%81%A7%E3%81%97%E3%81%9F%E3%80%82%E3%82%B3%E3%83%AC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E5%90%8D(blog-search)%E3%81%A8%E3%82%BF%E3%82%A4%E3%83%97(Vector%20search)%E3%82%92%E9%81%B8%E3%82%93%E3%81%A7%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B%E3%81%A0%E3%81%91%E3%80%82%E6%9A%97%E5%8F%B7%E5%8C%96%E3%83%BB%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF%E3%83%BB%E3%83%87%E3%83%BC%E3%82%BF%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%81%AE%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC%E3%82%92%E5%85%A8%E9%83%A8%E8%87%AA%E5%8B%95%E3%81%A7%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%8F%E3%82%8C%E3%81%A6%E3%80%81%E6%95%B0%E5%88%86%E3%81%A7%20Active%20%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82.png)
■ データを入れる:チャンク分割 → ベクトル化 → 投入
ここからは Python スクリプトで。流れは3ステップです。
● ① チャンク分割:
記事をまるごと1件にすると検索が雑になるので、見出し単位で細かく区切ります。この「長い文章をどう切るか」は、実は RAG の肝でもあります。
● ② ベクトル化:
各チャンクを Bedrock の Titan Embed v2(amazon.titan-embed-text-v2:0、1024次元)で数値の配列に変換します。
def embed(text):
resp = bedrock.invoke_model(
modelId="amazon.titan-embed-text-v2:0",
body=json.dumps({"inputText": text}),
)
return json.loads(resp["body"].read())["embedding"] # 1024次元
● ③ 投入:
ベクトル付きで OpenSearch に入れます。インデックスのマッピングはこれだけ:
"mappings": {
"properties": {
"embedding": {"type": "knn_vector", "dimension": 1024},
"title": {"type": "text"},
"text": {"type": "text"},
"source":{"type": "keyword"},
}
}
ここで 次世代版の違いをひとつ。
旧版だと engine(faiss など)を指定していましたが、次世代版 では engine を省略してもそのまま動きました(指定しなくても適切に処理される)。マッピングがシンプルになりました。
記事28+実装メモ32=計60チャンクを投入。成功60・エラー0で入りました。
■ 意味で検索する(本題)
では検索。クエリも Titan Embed でベクトル化して、k-NN(最近傍)検索を投げます。
結果がこれ:
● 検索「AIのモデルが東京で使えない件」
→ 1位「① モデル指定が、そのままでは通らない」(第1弾の記事)
…「Claude」とも「Bedrock」とも入力していないのに、意味でちゃんと当てました。これがキーワード検索との違いです。
● 検索「ストリーミングを自前で作るのは大変なのか」
→ 1位「ストリーミングは、1行も書いていない」
これも完璧。一方で、面白い"外し方"もしました。
● 検索「チャットの返事が二重に表示されるバグ」
→ 上位に「Markdown描画の問題」「空のAssistantが出る問題」など表示まわりの悩みがまとまってヒット
狙っていた「空Assistant」のセクションは1位ではなく、関連トピックが束になって出てきました。ベクトル検索は「意味が近いもの」を集めるので、完璧な一撃ではないけれど、関連する話題をまとめて拾ってくる。この挙動が体感できたのは収穫でした。
■ コストを観察(Scale-to-Zero は本当か)
一番知りたかったところ。
CloudWatch の IndexingOCU / SearchOCU(collection group 単位)を1分刻みで追いました。
|
状態 |
IndexingOCU |
SearchOCU |
|
触る前(アイドル) |
0 |
0 |
|
投入・検索中 |
1 |
2 |
|
最後の操作から約10分後 |
0 |
0 |
触れ込みどおり、アイドルで本当に0 OCU。
そして使い終わってから約10分で0に戻りました(公式の idle timeout 10分どおり)。
面白かったのは、Indexing と Search が別々に idle 判定されていたこと。先に使い終わった投入(Indexing)が先に0、検索(Search)が後から0、と独立して落ちていきました。
今回の検証で動いた OCU は最大でも 1+2=3 OCU、時間にして十数分。
$0.24/OCU時 換算で、コストは数十円レベルでした。推測値ですが、($0.24 × 3 OCU × 0.25h ≈ $0.18 ≈ 約27円)ほどです。
旧版の「常時最低2OCU(放置で月数百ドル)」を思うと、別物です。
$0.24 は us-east-1 の値です。
■ 詰まったところ
● ① CLIユーザーに権限が無くて 403
Express Create が自動で作るデータアクセスポリシーは、作成した本人(コンソールのログインユーザー)にだけ権限を付けます。スクリプトを動かす CLI のユーザーは別人だったので、いきなり 403。CLIユーザー向けのデータアクセスポリシーを別途作って解決しました。「誰がアクセスするか」を最初に意識しておくべきでした。
● ② 大量投入でタイムアウト
60チャンク(各1024次元)を一括投入したら、クライアントの**読み取りタイムアウト(既定10秒)**を超えて失敗。タイムアウトを延ばし、**小分け(チャンク10件ずつ)**にして解決しました。ベクトルは1件が地味に大きいので、まとめて送りすぎないのがコツ。
● ③ 後片付けで collection group が消せない
検証後、コレクションとポリシーは CLI で削除できましたが、自動生成された collection group が、手元の(やや古い)CLIだと削除コマンドが無く消せませんでした。空の collection group は OCU 0 なので課金はされませんが、完全に消すならコンソールから、というオチ。**「作るのは一瞬、片付けは意外と気を遣う」**のはクラウドあるあるです。
■ 感想
「アイドルで0円」は本当でした。
しかも実測で「最後のリクエストから約10分で0」まで見えたのは、触ってみた甲斐がありました。旧版の常時課金を知っていると、Scale-to-Zero は素直にうれしい。
そして、自分のブログが「意味で引ける」ようになったのが地味に楽しい。キーワードを思い出せなくても、ふわっとした言い回しで過去の自分のメモにたどり着ける。
このベクトル検索を、第1弾のチャットボットに組み込んで RAG にします。
「自分のブログに質問すると、該当箇所を検索して答えてくれるAI」つまり「過去の自分に相談できるチャット」を作るのが目標です。