免責
本記事は筆者個人の見解であり、所属組織の公式見解・開発体制・業務内容を示すものではありません。記事内の技術的表現は執筆時点(2026年6月)の実機検証に基づくものであり、特定の製品・サービスの性能・仕様を保証するものではありません。アカウントID・リソース名はマスク(アカウントIDは 123456789012)しています。
こんにちは!Dirbatoの社内技術横断支援組織Backbeatに所属している柴田です!
OpenSearch や pgvector を立てるほどじゃないけど、RAG用のベクトルは持っておきたい――そんな「常時起動のベクトルDBを抱えるのはコスト的にツライ」問題に、AWS がストレージ課金型で答えたのが Amazon S3 Vectors です。
本記事では、マネージドサービスやフレームワーク(LangChain等)に頼らず、素の S3 Vectors API だけでベクトルバケット作成 → 埋め込み投入 → 類似検索 → メタデータフィルタまでを実機で通し、**「結局どこが嬉しいのか」「最小権限でハマるポイントはどこか」**を実測ベースで整理します。
要点サマリー(結論先出し)
| # | 検証したこと | 結果 |
|---|---|---|
| 1 | バケット/インデックス作成(1024次元・cosine・float32) | ✅ APIコール数発で完了。暗号化はデフォルトでSSE-S3が有効 |
| 2 | 日本語12文書を Titan v2 で埋め込み→投入→類似検索 | ✅ 4クエリすべてで top-1 が意図通りのサービスにヒット |
| 3 | メタデータフィルタ($eq/$gte/$lte/$in/$ne/$and) |
✅ MongoDB風演算子がひと通り正しく動作 |
| 4 | 最小権限の落とし穴 | 🚨 QueryVectors でフィルタ/メタデータ取得をするなら s3vectors:GetVectors も必須(無いと403) |
| 5 | コスト構造 | ✅ 常時起動コンピュートを持たない=idle時コストがほぼストレージ代だけ |
ひとことで: S3 Vectors は「低頻度アクセスの大量ベクトルを安く寝かせておける、RAG向けのベクトル保管庫」。リアルタイム高QPS用途は引き続き OpenSearch 等の出番。
前提知識:ベクトル検索の基礎(3分で)
S3 Vectors の本題に入る前に、「ベクトル検索」の土台を3つだけ押さえます。ここが分かると、後半の「1024次元」「cosine距離」が一気に読めます。
① 埋め込み(embedding)= テキストを「意味の座標」に変換する
文章や画像を、意味を表す**数百〜数千個の数字の並び(=ベクトル)**に変換することを 埋め込み(embedding)、変換するAIを 埋め込みモデル と呼びます。キモは 「意味が近いものは、数字の並びも近くなる」 こと。
② 次元(dimension)= ベクトルの数字の個数
「1024次元」とは、1つのベクトルが 1024個の数字でできている という意味です。多いほど意味を細かく表せますが、容量と計算量も増えます。Titan V2 は 256 / 512 / 1024 から選べ、本記事は 1024 を使用。この次元はインデックス作成時に固定し、後から変えられません(使う埋め込みモデルと次元は最初に決める)。
③ 距離(distance)= 近さのものさし
検索とは「クエリのベクトルに 近い ベクトルを探す」だけのこと。近さの測り方が 距離メトリック で、文章の意味検索では cosine(ベクトルの向き=角度の近さ) が定番です(S3 Vectors は cosine / euclidean から選択)。
📏 重要:距離は小さいほど似ている。 後半の結果表に出てくる
lambda (0.684)やdynamodb (0.387)は cosine 距離で、値が小さいほど自信を持って一致していることを表します。
もっと噛み砕いた前提(RAGの全体像・用語集つき)が欲しい方向けに、別添の入門HTML(
s3-vectors-primer.html)も用意しています。
Amazon S3 Vectors とは
S3 にネイティブなベクトルストレージが追加されたもので、以下の3階層で構成されます。
ポイントは、OpenSearch Serverless のような「常時起動の検索コンピュート(OCU)」を持たないこと。課金は概ね「保存しているベクトル量 + リクエスト数」で、使っていない間のコストが小さく済みます。
生成AI と RAG、その中での S3 Vectors の位置
典型的な使いどころが RAG(Retrieval-Augmented Generation:検索拡張生成) です。生成AI(LLM)は、学習していない最新情報や社内情報を知りません。そこで 「まず関連文書を検索し、その内容を“根拠”として質問と一緒にLLMへ渡す」 ことで、生成AIに正確な回答をさせます。この 検索を担う“ベクトルの保管庫”が S3 Vectors です(Amazon Bedrock Knowledge Bases のベクトルストアとしても選択可)。
「生成AIが賢く答える」裏側で、S3 Vectors は②の検索部分(Retrieval)を支える地味だが重要な部品――という関係です。
検証環境
| 項目 | 値 |
|---|---|
| リージョン |
ap-northeast-1(東京) |
| 埋め込みモデル |
amazon.titan-embed-text-v2:0(1024次元・normalize=true) |
| インデックス | dataType=float32 / dimension=1024 / distanceMetric=cosine
|
| コーパス | 日本語のAWSサービス説明 12件(メタデータ category / year / service / text) |
ハンズオン①:ベクトルバケットとインデックスを作る
# 1) ベクトルバケット作成
aws s3vectors create-vector-bucket \
--vector-bucket-name s3vectors-poc-123456789012 \
--region ap-northeast-1
# 2) インデックス作成(次元と距離メトリックはここで固定)
# text は「非フィルタ対象メタデータ」にして、フィルタ用2KB枠を圧迫させない
aws s3vectors create-index \
--vector-bucket-name s3vectors-poc-123456789012 \
--index-name aws-services-idx \
--data-type float32 \
--dimension 1024 \
--distance-metric cosine \
--metadata-configuration '{"nonFilterableMetadataKeys":["text"]}' \
--region ap-northeast-1
get-index で確認すると、暗号化は指定しなくても SSE-S3(AES256)がデフォルト適用されていました。
{
"dataType": "float32",
"dimension": 1024,
"distanceMetric": "cosine",
"metadataConfiguration": { "nonFilterableMetadataKeys": ["text"] },
"encryptionConfiguration": { "sseType": "AES256" }
}
ハンズオン②:埋め込み → 投入 → 検索(boto3)
ベクトルデータは float32 で渡すのが作法(高精度型を渡すと内部で float32 に変換されます)。data / queryVector は {"float32": [...]} というUnion型です。
import json, boto3
REGION = "ap-northeast-1"
BUCKET = "s3vectors-poc-123456789012"
INDEX = "aws-services-idx"
bedrock = boto3.client("bedrock-runtime", region_name=REGION)
s3v = boto3.client("s3vectors", region_name=REGION)
def embed(text: str) -> list[float]:
body = json.dumps({"inputText": text, "dimensions": 1024, "normalize": True})
r = bedrock.invoke_model(modelId="amazon.titan-embed-text-v2:0", body=body)
return json.loads(r["body"].read())["embedding"]
# --- コーパス(抜粋)---
corpus = [
("lambda", "コンピュート", 2014, "AWS Lambdaはサーバーをプロビジョニングせずにコードを実行できるサーバーレスサービスです。"),
("bedrock", "AI/ML", 2023, "Amazon Bedrockは複数の基盤モデルを単一のAPIで利用できる生成AIのサービスです。"),
("dynamodb", "データベース", 2012, "Amazon DynamoDBは一桁ミリ秒の応答を実現するフルマネージドなNoSQLデータベースです。"),
# ... 全12件
]
# --- 投入(1リクエスト最大500ベクトル)---
vectors = [{
"key": key,
"data": {"float32": embed(text)},
"metadata": {"category": cat, "year": year, "service": key, "text": text},
} for key, cat, year, text in corpus]
s3v.put_vectors(vectorBucketName=BUCKET, indexName=INDEX, vectors=vectors)
# --- 類似検索 ---
qv = embed("サーバーレスでコードを実行したい")
res = s3v.query_vectors(
vectorBucketName=BUCKET, indexName=INDEX,
queryVector={"float32": qv},
topK=3, returnDistance=True, returnMetadata=True,
)
for v in res["vectors"]:
print(v["key"], round(v["distance"], 3))
検索結果(recall 実測)
日本語の口語クエリに対して、全クエリで top-1 が意図したサービスになりました(cosine距離は小さいほど類似)。
| クエリ | top-1(距離) |
|---|---|
| サーバーレスでコードを実行したい | lambda (0.684) |
| 生成AIの基盤モデルをAPIで使いたい | bedrock (0.561) |
| データを暗号化する鍵を管理したい | kms (0.665) |
| ミリ秒で応答するNoSQLデータベースが欲しい | dynamodb (0.387) |
レイテンシは「同一リージョンの EC2/Lambda から直接叩く」のがフェアな計測条件です(手元の踏み台経由だと往復が支配的になり、サービス本来の値になりません)。本記事では機能面(recall・フィルタ)を主成果とし、レイテンシ計測は上記コードをそのまま in-region で回す形を推奨します。
ハンズオン③:メタデータフィルタを「演算子網羅」で検証
S3 Vectors の filter は MongoDB ライクな条件式です。近傍探索とフィルタが AND 結合され、条件に合致する候補だけが返ります。代表的な演算子を一通り試しました。
# 例: category がデータベースのものだけを近傍探索
res = s3v.query_vectors(
vectorBucketName=BUCKET, indexName=INDEX,
queryVector={"float32": embed("高速で拡張性のあるデータベース")},
topK=5, returnDistance=True, returnMetadata=True,
filter={"category": {"$eq": "データベース"}},
)
| フィルタ | 返却された key | 判定 |
|---|---|---|
{"category": {"$eq": "データベース"}} |
dynamodb, aurora のみ | ✅ |
{"year": {"$gte": 2015}} |
aurora, ecs, sagemaker, bedrock | ✅ |
{"$and": [{"category": {"$in": ["ストレージ","ネットワーク"]}}, {"year": {"$lte": 2009}}]} |
s3, ebs, vpc, cloudfront | ✅ |
{"category": {"$ne": "セキュリティ"}} |
iam/kms を除外 | ✅ |
比較($gte/$lte)・集合($in)・否定($ne)・論理結合($and)まで素直に動作。「ベクトル近傍 × 構造化属性の絞り込み」が1リクエストで完結するのは実装上かなり楽です。
🚨 ハマりどころ:最小権限だと検索が 403 になる
ここが地味に重要な落とし穴です。QueryVectors の必要権限はリクエスト内容で変わります。
| やりたいこと | 必要なIAMアクション |
|---|---|
近傍のキーと距離だけ取得(returnMetadata 未指定) |
s3vectors:QueryVectors のみ |
メタデータフィルタを使う / returnMetadata=true |
s3vectors:QueryVectors + s3vectors:GetVectors
|
つまり「検索できるけど、フィルタを付けた途端に 403」という事故が起きます。実際にフィルタ・メタデータ返却を多用した本検証では、両方を付与して初めて成功しました。
最小権限ポリシーはインデックス単位で書けます(ARN は bucket/<名前>/index/<名前> の階層)。アプリ用途では読み取り専用インデックスと書き込み可インデックスを分離できます。
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "QueryWithFilter",
"Effect": "Allow",
"Action": ["s3vectors:QueryVectors", "s3vectors:GetVectors"],
"Resource": "arn:aws:s3vectors:ap-northeast-1:123456789012:bucket/s3vectors-poc-123456789012/index/aws-services-idx"
}]
}
主要な制約(公式)
| 項目 | 上限 |
|---|---|
| ベクトル / インデックス | 最大 20億 |
| 次元 | 1〜4,096 |
| メタデータ合計 / ベクトル | 40KB(うちフィルタ可能分は最大 2KB) |
| メタデータキー数 / ベクトル | 最大 50 |
| 非フィルタメタデータキー / インデックス | 最大 10 |
| 書込スループット / インデックス | 1,000 req/s(2,500ベクトル/s) |
| 1リクエストのベクトル数(Put/Delete) | 最大 500 |
| topK | 最大 100 |
| リクエストペイロード | 最大 20MiB |
「フィルタに使うメタデータは 2KB まで」「非フィルタキーは作成時に宣言(最大10)」あたりは設計時に効いてくるので、長文の原文は非フィルタキーに逃がすのが定石です。
既存ベクトルDBとの比較(コスト構造の観点)
| 観点 | S3 Vectors | OpenSearch Serverless (Vector) | Aurora PostgreSQL + pgvector |
|---|---|---|---|
| 課金モデル | ストレージ + リクエスト(常時起動コンピュートなし) | OCU(コンピュート)常時課金・最小キャパシティの下限あり | DBインスタンス常時課金 |
| idle時コスト | ほぼ保存量のみ=小 | 大(最小OCUが回り続ける) | 中〜高 |
| 得意領域 | 大量・低頻度アクセスのベクトルを安く保管、Bedrock KB連携 | リアルタイム・高QPS・全文検索併用 | 既存RDBと結合・トランザクション |
| スケール | 20億ベクトル/インデックス | 大規模・高スループット | インスタンス性能依存 |
AWS は「ベクトルのアップロード・ストレージ・クエリにかかるコストを、専用のベクトルDBを使う場合と比べて最大90%削減できる」と説明しています(あくまでベクトル層のコストに限った数値で、システム総コストの話ではない点に注意。出典は末尾)。要は 「速さの最後の一押し」より「寝かせておくコスト」を取りに行く選択肢、という位置づけです。
各サービスの実額は料金改定が入るため、採用判断時は最新の pricing ページで試算してください。本記事はあくまで「課金モデルの構造的な違い」を整理したものです。
まとめ
- S3 Vectors は 素のAPIだけで RAG のベクトル層が完結する(バケット→インデックス→put→query)。
- 日本語コーパスでの recall は実用十分(Titan v2・1024次元・cosine で top-1 全正解)。
- メタデータフィルタは演算子が一通り使えるので、属性で絞った近傍探索が1コールで書ける。
-
最小権限の罠:フィルタ/メタデータ取得には
GetVectorsも必要。 - 嬉しさの本質はコスト構造:常時起動コンピュートを持たないので、低頻度アクセスのベクトルを安く保管できる。リアルタイム高QPS は引き続き OpenSearch 等。
「とりあえず RAG のベクトルを置いておく場所」として、まず S3 Vectors から始めて、レイテンシ/QPS要件がシビアになったら他を検討する――という入り方がしやすくなったと感じました。
