Amazon DynamoDBの新機能「ベクトルデータサポート」をCLIで試してみた

Amazon DynamoDBの新機能「ベクトルデータサポート」をCLIで試してみた

Amazon DynamoDBのベクトルデータサポートがGAになりました。事前に用意したベクトルを既存アイテムに保存し、GSIとは別種のベクトルインデックスを作成して、SearchVectors による近似近傍探索までをAWS CLIで確認します。
2026.08.06

はじめに

2026年8月5日、Amazon DynamoDBのベクトルデータサポートがGAになりました。全商用リージョンとAWS GovCloud (US) で提供されます。

https://aws.amazon.com/about-aws/whats-new/2026/08/amazon-dynamodb-vector-search

DynamoDBはベクトルの保存と検索を担いますが、ベクトルそのものは生成しません。別途用意したベクトルを、アイテムの属性に数値のリスト(L型の中にN型の要素を並べた形)として書き込みます。そのうえでベクトルインデックスを作成すると、SearchVectorsで類似したアイテムを近似近傍探索(ANN)で取得できます。

この記事では、ベクトルデータが手元にある状態から、DynamoDBへの登録、ベクトルインデックスの作成、SearchVectorsでの検索までを確認します。

ベクトルインデックスはGSIとは別種のインデックスです。主な違いは次のとおりです。

項目 ベクトルインデックス GSI
クエリ種別 類似検索 完全一致・範囲
読み取りAPI SearchVectorsのみ QueryScan
テーブルあたりの最大数 5 20
容量モード オンデマンド専用 両対応
検索条件の演算子 等価のみ 範囲も可

ベーステーブルの標準料金に加え、ベクトルの書き込み・検索・ストレージの3つが加算されます。ベクトル検索で処理されるデータ量は、インデックスのサイズとともに増えます。

検証内容

検証環境

  • リージョン: 東京(ap-northeast-1
  • AWS CLI: 2.36.16
  • DynamoDB容量モード: オンデマンド(PAY_PER_REQUEST
  • 既存ベクトル: 1024次元のfloat32(インデックス内はf32精度で保存される)

既存のベクトルデータを登録用アイテムにする

今回は次の記事で準備したベクトルをそのまま使いました。

https://dev.classmethod.jp/articles/vector-db-less-semantic-search/

NumPy形式のベクトルデータと、ベクトルIDを含む記事のメタデータを突き合わせて、登録用アイテムを組み立てました。

ベクトルデータは60,785件・1024次元です。要約を持つ日本語記事を抽出すると41,113件でした。そこからベクトルIDと記事IDが対応するものを、公開日の新しい順に20,000件選びました。

登録用アイテムは次の構造にしました。embeddingは先頭3要素のみ示します。

{
  "article_id": { "S": "example-article-id" },
  "title": { "S": "記事タイトル" },
  "language": { "S": "ja" },
  "published_year": { "N": "2025" },
  "embedding": {
    "L": [
      { "N": "0.012345" },
      { "N": "-0.067890" },
      { "N": "0.123456" },
      ...
    ]
  }
}

ベクトルインデックスを作成する

上記の20,000件をテーブルに登録したあと、ベクトルインデックスを作成しました。既存テーブルへの追加はupdate-table--vector-index-updatesで行います。テーブル作成と同時に作る場合はcreate-table--vector-indexesを使います。

aws dynamodb update-table \
  --table-name ArticleVectorSearch \
  --attribute-definitions \
    '[{"AttributeName":"language","AttributeType":"S"},
      {"AttributeName":"published_year","AttributeType":"N"}]' \
  --vector-index-updates \
    '[{"Create":{
        "IndexName":"ArticleEmbeddingIndex",
        "VectorAttribute":{"AttributeName":"embedding"},
        "SearchSchema":[
          {"AttributeName":"language","SearchSchemaElementType":"HASH"},
          {"AttributeName":"published_year","SearchSchemaElementType":"INLINE_FILTER"}],
        "Projection":{"ProjectionType":"ALL"},
        "Dimensions":1024,
        "DistanceFunction":"COSINE"}}]'

SearchSchemaで指定する属性は--attribute-definitionsにも宣言が必要です。宣言を省くとOne element in SearchSchema is not defined in attribute definitionsが返ります。また、1回のUpdateTableで追加または削除できるベクトルインデックスは1つです。削除する場合はDeleteIndexNameだけを指定します。

作成後の設定を確認しました。

  • 次元数: 1024
  • 距離関数: COSINE
  • SearchSchema: languageHASHpublished_yearINLINE_FILTER
  • 投影: ALL
  • IndexStatus: ACTIVE

HASHに指定したlanguageは、検索対象を1つのパーティションキー値に限定します。

公式仕様ではBackfillingtrueの間はSearchVectorsが使えないため、BackfillingIndexStatusと併せて確認します。次元数・距離関数・SearchSchemaは作成後に変更できないため、変更する場合は新しいベクトルインデックスを作成して移行します。

保存済みベクトルをSearchVectorsで検索する

SearchVectorsの実行には、インデックスのリソースARNに対するdynamodb:SearchVectors権限が必要です。Fine-Grained Access Control(FGAC)の条件キーはSearchVectorsに適用されないため、アクセス制御はインデックス単位で行います。

クエリベクトルの指定には--search-vectorを使いますが、1024個の数値をコマンドラインに直接書き込むのは非現実的です。DynamoDBのリスト形式([{"N":"..."}, ...])でファイルに保存し、file://で参照します。今回はインデックスに入っている記事のベクトルをクエリに使うため、get-itemで取り出したembeddingをリスト形式のまま書き出しました。

# クエリに使う保存済みベクトルを、リスト形式のままファイルへ書き出す
aws dynamodb get-item \
  --table-name ArticleVectorSearch \
  --key '{"article_id":{"S":"<記事ID>"}}' \
  --projection-expression "embedding" \
  | jq -c '.Item.embedding.L' > query-vector.json

query-vector.json{"N":"<値>"}が1024個並んだ配列で、今回は1行で約22 KBのJSONになりました。先頭3要素だけを示します。

[{"N":"-0.00135588495"},{"N":"0.00375213451"},{"N":"-0.00101051806"}, ... ]
aws dynamodb search-vectors \
  --table-name ArticleVectorSearch \
  --index-name ArticleEmbeddingIndex \
  --search-vector file://query-vector.json \
  --search-condition-expression "#lang = :lang" \
  --expression-attribute-names '{"#lang":"language"}' \
  --expression-attribute-values '{":lang":{"S":"ja"}}' \
  --projection-expression "article_id,title,published_year" \
  --top-k 11

languageはDynamoDBの予約語のため、#langで参照しています。

SearchSchemaにHASHがある場合はSearchConditionExpressionを省略できません。省略するとSearchConditionExpression must be provided when SearchSchema has a HASH keyが返ります。INLINE_FILTERのpublished_yearpublished_year = :yのように等価で絞り込めます。>=>ではInvalid comparator used in SearchConditionExpressionBETWEENではInvalid operator used in SearchConditionExpressionとなりました。いずれもValidationExceptionです。

レスポンスはSearchResultsの配列で、各要素がItemScoreを持ちます。COSINEではScoreが小さいほど類似で、0が最も類似(ベクトルの向きが一致)です。クエリに使ったベクトルはインデックスにも入っているため、検索元の1件を除外する前提でTopKは11としています。上位3件を抜粋します。

{
  "SearchResults": [
    {
      "Item": {
        "article_id": { "S": "<記事ID>" },
        "published_year": { "N": "2026" },
        "title": { "S": "EC2 の基本モニタリングと詳細モニタリングで CloudWatch アラームの挙動を比較してみた" }
      },
      "Score": 0.0
    },
    {
      "Item": {
        "article_id": { "S": "<記事ID>" },
        "published_year": { "N": "2024" },
        "title": { "S": "EC2インスタンスの基本モニタリングでもデータポイントは1分間隔で送信されている件" }
      },
      "Score": 0.0932389348745346
    },
    {
      "Item": {
        "article_id": { "S": "<記事ID>" },
        "published_year": { "N": "2024" },
        "title": { "S": "[小ネタ] Lambda のメトリクスは 1 分間隔という話" }
      },
      "Score": 0.14334873855113983
    }
  ]
}

保存済みベクトル10件で同じ条件を試し、すべてで検索元の記事がScore 0.0の1位に返りました。検索元を除いた10件を関連記事として扱いました。

テーブルへの書き込みと削除がベクトルインデックスへ反映されることも確認しました。検証用のアイテムを1件put-itemし、1秒間隔で同じ検索を繰り返して反映を待ちます。

aws dynamodb put-item \
  --table-name ArticleVectorSearch \
  --item file://verify-item.json

# search-vectors の引数は前掲と同じ。検証用アイテムが返るまで1秒間隔で待つ
for i in $(seq 1 30); do
  sleep 1
  aws dynamodb search-vectors ... \
    | jq -e '.SearchResults[] | select(.Item.article_id.S == "verify-sync-0806")' && break
done

1回目のポーリングで一致しました。put-itemの実行、1秒の待機、search-vectorsの実行を合わせて2秒未満なので、反映はその範囲で完了しています。delete-itemのあとは判定を反転させ、一致しなくなるまで待ちました。こちらも1回目のポーリングで結果から消えていました。上位2件のarticle_idScoreを抜粋します。

# put-item 後
verify-sync-0806  0.0
<記事ID>          0.0

# delete-item 後
<記事ID>          0.0
<記事ID>          0.0932389348745346

1万件と2万件のCLI実行時間を比較する

20,000件は一度に登録していません。同じテーブルに10,000件を登録した時点で計測し、そのあと10,001〜20,000件目を追加して同じ計測を行いました。前節で使った保存済みベクトル10件を、1万件時・2万件時に各1回検索しました。計測対象はaws dynamodb search-vectorsのプロセス開始からレスポンス取得までです。

1万件 2万件 差分(2万件−1万件)
1 470.984 ms 492.560 ms +21.576 ms
2 472.815 ms 442.878 ms -29.937 ms
3 441.232 ms 469.128 ms +27.896 ms
4 484.792 ms 455.368 ms -29.424 ms
5 438.733 ms 480.778 ms +42.045 ms
6 444.719 ms 458.544 ms +13.825 ms
7 436.427 ms 474.685 ms +38.258 ms
8 443.344 ms 453.708 ms +10.364 ms
9 437.117 ms 451.369 ms +14.252 ms
10 439.034 ms 463.014 ms +23.980 ms
集計 平均450.920 ms 平均464.203 ms 平均差+13.283 ms(約2.9%)
  • 1万件時の範囲: 436.427〜484.792 ms
  • 2万件時の範囲: 442.878〜492.560 ms

各クエリの実行は1回のみで、CLIの起動やネットワーク往復を含み、クエリ用JSONの生成時間は含みません。DynamoDB側の処理時間を切り出したものではなく、実行環境やネットワーク条件で変動します。

まとめ

ベクトル化済みのデータが手元にあれば、DynamoDBが規定する数値リスト形式で既存アイテムに保存し、ベクトルインデックスを作成するだけでSearchVectorsによる類似検索まで到達できました。

アイテム本体とベクトルを同じテーブルで扱えるのが利点です。Amazon S3 Vectorsなど別のデータストアを併用すると同期が課題になりがちな更新・削除も、ベクトルインデックスへ自動で反映されるため、シンプルな利用が期待できます。

参考リンク

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事