
I tried the trending embedded vector database LanceDB with Amazon S3 and Amazon Bedrock
This page has been translated by machine translation. View original
I am Ishikawa from the Cloud Business Division. Since it was a 3-day weekend, I tried running the trending embedded vector database LanceDB with an Amazon S3 backend, and combined it with Amazon Bedrock to test vector search and simple RAG.
When you want to use vector search in generative AI applications, there are various options such as Amazon OpenSearch Service and Amazon S3 Vectors. Among them, LanceDB is a file-based vector database that can be embedded into applications like SQLite, and features the ability to specify Amazon S3 directly as the data storage destination. Since you can have a vector search infrastructure with only S3 storage costs without setting up any DB server, it is an architecture that works well with serverless configurations.
The AWS Architecture Blog introduces a case where Metagenomi built a search infrastructure for 3.5 billion protein vectors with 960 dimensions using the configuration of LanceDB + Amazon S3 + AWS Lambda.
DevelopersIO already has an article explaining how LanceDB works when running it in a local environment. This article focuses on using it with an S3 backend and combining it with Amazon Bedrock.
What is LanceDB
LanceDB is an open-source embedded vector database built on top of the columnar format "Lance". It is an open-source embedded search library and vector database for multimodal AI. The main features are as follows.
- Serverless and embedded: It can be introduced with just
pip install lancedb, and there is no need to set up a separate database server. Data is stored in a local directory (or object storage) - Lance format as the foundation: Built on the Rust-based columnar format "Lance", and according to the official site, it achieves 100x faster random access compared to Parquet
- Diverse search methods: In addition to vector similarity search, it supports full-text search and metadata filtering using SQL syntax
- Automatic versioning: Changes to tables are automatically version-controlled, allowing you to roll back to previous states
- Multi-language SDKs: SDKs for Python / TypeScript / Rust and a REST API are provided
There is no need to start a separate DB server; it operates within the application process and data is saved as files. In addition to the local file system, you can specify object storage such as Amazon S3 as the storage destination.
When using S3 as the storage destination, simply set the connection string to s3://bucket-name/path. The required IAM permissions are s3:PutObject, s3:GetObject, s3:DeleteObject, s3:ListBucket, and s3:GetBucketLocation as the basic set. Also, with the current atomic write support for S3, concurrent writes to the same table can now be handled without an external commit coordinator.
The configuration to be tested this time is as follows. Amazon Titan Text Embeddings V2 is used for embedding generation, Claude Haiku 4.5 is used for RAG answer generation, and LanceDB on S3 handles vector storage and search.
For those who want to learn more about what Lance / LanceDB is, this blog is recommended.
Trying it out
Prerequisites
- Use Titan Text Embeddings V2 and Claude Haiku 4.5 on Amazon Bedrock
- Verification environment: ap-northeast-1
- Python 3.14.0, lancedb 0.34.0, pyarrow 25.0.0, boto3 1.43.49
Install the required packages locally in the project using uv.
uv init
uv add lancedb pyarrow boto3 pandas
Verifying connectivity with Bedrock models
First, confirm that the embedding model can be called.
% aws bedrock-runtime invoke-model \
--model-id amazon.titan-embed-text-v2:0 \
--body '{"inputText":"LanceDB のテストです","dimensions":1024,"normalize":true}' \
--cli-binary-format raw-in-base64-out \
--region ap-northeast-1 \
response.json
{
"contentType": "application/json"
}
% python3 -c "import json; d=json.load(open('response.json')); print('embedding_dim:', len(d['embedding'])); print('inputTextTokenCount:', d.get('inputTextTokenCount'))"
embedding_dim: 1024
inputTextTokenCount: 8
A 1024-dimensional embedding vector was returned. Claude Haiku 4.5 (jp.anthropic.claude-haiku-4-5-20251001-v1:0) has also been confirmed to respond via the converse API.
Creating an S3 bucket
Create a bucket to serve as the data storage destination for LanceDB. Adjust the bucket name to make it unique.
% aws s3 mb s3://cm-lancedb-blog-20260720 \
--region ap-northeast-1
make_bucket: cm-lancedb-blog-20260720
% aws s3api put-public-access-block \
--bucket cm-lancedb-blog-20260720 \
--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
Encryption is left at the default SSE-S3, and S3 versioning is not configured. As described later, LanceDB manages its own versioning, so S3-side versioning is not required.
Generating embeddings and creating a LanceDB table
As sample data, I prepared 40 descriptions of AWS services. The JSON is as follows.
sample_docs.json
[
{"id": "doc-01", "title": "Amazon S3", "text": "Amazon S3 は、業界をリードするスケーラビリティ、データ可用性、セキュリティ、パフォーマンスを提供するオブジェクトストレージサービスです。データレイク、バックアップ、アーカイブなど幅広い用途で利用されます。"},
{"id": "doc-02", "title": "AWS Lambda", "text": "AWS Lambda は、サーバーのプロビジョニングや管理なしでコードを実行できるサーバーレスコンピューティングサービスです。イベント駆動型のアプリケーション構築に適しています。"},
{"id": "doc-03", "title": "Amazon EC2", "text": "Amazon EC2 は、クラウド上で仮想サーバーを提供するサービスです。多様なインスタンスタイプから用途に応じた計算リソースを選択できます。"},
{"id": "doc-04", "title": "Amazon DynamoDB", "text": "Amazon DynamoDB は、フルマネージドの NoSQL データベースサービスです。1桁ミリ秒のレイテンシで、あらゆる規模のアプリケーションに対応します。"},
{"id": "doc-05", "title": "Amazon RDS", "text": "Amazon RDS は、リレーショナルデータベースをクラウド上で簡単にセットアップ、運用、スケーリングできるマネージドサービスです。MySQL、PostgreSQL、Oracle などに対応しています。"},
{"id": "doc-06", "title": "Amazon Redshift", "text": "Amazon Redshift は、クラウドデータウェアハウスサービスです。ペタバイト規模のデータに対して高速な分析クエリを実行できます。Serverless オプションも提供されています。"},
{"id": "doc-07", "title": "Amazon Athena", "text": "Amazon Athena は、S3 上のデータに対して標準 SQL でクエリを実行できるサーバーレスのインタラクティブ分析サービスです。スキャンしたデータ量に応じた課金体系です。"},
{"id": "doc-08", "title": "AWS Glue", "text": "AWS Glue は、サーバーレスのデータ統合サービスです。ETL ジョブの実行、データカタログの管理、データ品質の検証などが可能です。"},
{"id": "doc-09", "title": "Amazon Bedrock", "text": "Amazon Bedrock は、主要な AI 企業が提供する高性能な基盤モデルを単一の API で利用できるフルマネージドサービスです。テキスト生成や埋め込みベクトルの生成が可能です。"},
{"id": "doc-10", "title": "Amazon SageMaker", "text": "Amazon SageMaker は、機械学習モデルの構築、トレーニング、デプロイを支援するフルマネージドサービスです。データサイエンティストと開発者向けの統合開発環境を提供します。"},
{"id": "doc-11", "title": "Amazon OpenSearch Service", "text": "Amazon OpenSearch Service は、ログ分析や全文検索、ベクトル検索を実行できるマネージド検索サービスです。k-NN 検索によるセマンティック検索にも対応しています。"},
{"id": "doc-12", "title": "Amazon Kinesis Data Streams", "text": "Amazon Kinesis Data Streams は、ストリーミングデータをリアルタイムに収集・処理できるサービスです。大規模なデータストリームを低レイテンシで扱えます。"},
{"id": "doc-13", "title": "Amazon QuickSight", "text": "Amazon QuickSight は、クラウドネイティブな BI サービスです。ダッシュボードの作成や、自然言語による質問応答機能 Amazon Q in QuickSight を利用できます。"},
{"id": "doc-14", "title": "AWS Step Functions", "text": "AWS Step Functions は、分散アプリケーションのワークフローを視覚的に構築できるサーバーレスオーケストレーションサービスです。Lambda などの AWS サービスを連携させられます。"},
{"id": "doc-15", "title": "Amazon API Gateway", "text": "Amazon API Gateway は、REST API や WebSocket API を作成、公開、管理できるフルマネージドサービスです。Lambda と組み合わせたサーバーレス API の構築によく使われます。"},
{"id": "doc-16", "title": "Amazon CloudFront", "text": "Amazon CloudFront は、低レイテンシでコンテンツを配信するグローバルな CDN サービスです。エッジロケーションを利用して静的・動的コンテンツを高速配信します。"},
{"id": "doc-17", "title": "Amazon VPC", "text": "Amazon VPC は、AWS クラウド内に論理的に分離されたプライベートネットワーク空間を構築するサービスです。サブネットやルートテーブルを自由に設計できます。"},
{"id": "doc-18", "title": "AWS IAM", "text": "AWS IAM は、AWS リソースへのアクセスを安全に管理するサービスです。ユーザー、グループ、ロール、ポリシーによるきめ細かなアクセス制御が可能です。"},
{"id": "doc-19", "title": "Amazon CloudWatch", "text": "Amazon CloudWatch は、AWS リソースとアプリケーションのモニタリングサービスです。メトリクスの収集、ログの分析、アラームの設定ができます。"},
{"id": "doc-20", "title": "AWS CloudFormation", "text": "AWS CloudFormation は、インフラをコードとして管理する IaC サービスです。テンプレートを使って AWS リソースをプロビジョニングできます。"},
{"id": "doc-21", "title": "Amazon ECS", "text": "Amazon ECS は、コンテナ化されたアプリケーションを実行するフルマネージドなコンテナオーケストレーションサービスです。Fargate と組み合わせるとサーバーレスでコンテナを実行できます。"},
{"id": "doc-22", "title": "Amazon EKS", "text": "Amazon EKS は、マネージドな Kubernetes サービスです。Kubernetes コントロールプレーンの運用を AWS に任せて、コンテナワークロードを実行できます。"},
{"id": "doc-23", "title": "AWS Fargate", "text": "AWS Fargate は、サーバーを管理せずにコンテナを実行できるサーバーレスコンピューティングエンジンです。ECS および EKS で利用できます。"},
{"id": "doc-24", "title": "Amazon SQS", "text": "Amazon SQS は、フルマネージドのメッセージキューイングサービスです。マイクロサービス間の疎結合な非同期通信を実現します。"},
{"id": "doc-25", "title": "Amazon SNS", "text": "Amazon SNS は、フルマネージドの Pub/Sub メッセージングサービスです。アプリケーション間通知やモバイルプッシュ通知に利用できます。"},
{"id": "doc-26", "title": "Amazon EventBridge", "text": "Amazon EventBridge は、イベント駆動型アーキテクチャを構築するためのサーバーレスイベントバスサービスです。SaaS アプリケーションとの連携も可能です。"},
{"id": "doc-27", "title": "AWS KMS", "text": "AWS KMS は、暗号化キーの作成と管理を行うマネージドサービスです。多くの AWS サービスと統合されており、保管時のデータ暗号化に利用されます。"},
{"id": "doc-28", "title": "Amazon Aurora", "text": "Amazon Aurora は、クラウド向けに構築された MySQL および PostgreSQL 互換のリレーショナルデータベースです。高い性能と可用性を提供し、Serverless v2 にも対応しています。"},
{"id": "doc-29", "title": "Amazon ElastiCache", "text": "Amazon ElastiCache は、Redis および Memcached 互換のインメモリキャッシュサービスです。データベースの負荷軽減やセッション管理に利用されます。"},
{"id": "doc-30", "title": "AWS Secrets Manager", "text": "AWS Secrets Manager は、データベース認証情報や API キーなどのシークレットを安全に管理するサービスです。シークレットの自動ローテーションに対応しています。"},
{"id": "doc-31", "title": "Amazon S3 Tables", "text": "Amazon S3 Tables は、Apache Iceberg テーブルに最適化されたストレージを提供する S3 の機能です。分析ワークロード向けにテーブルデータを効率的に管理できます。"},
{"id": "doc-32", "title": "AWS Lake Formation", "text": "AWS Lake Formation は、セキュアなデータレイクを構築・管理するサービスです。テーブルや列レベルのきめ細かなアクセス制御を実現します。"},
{"id": "doc-33", "title": "Amazon EMR", "text": "Amazon EMR は、Apache Spark や Hive などのビッグデータフレームワークを実行するマネージドクラスタープラットフォームです。Serverless オプションも利用できます。"},
{"id": "doc-34", "title": "AWS Batch", "text": "AWS Batch は、バッチコンピューティングジョブを効率的に実行するフルマネージドサービスです。ジョブのスケジューリングとコンピューティングリソースの管理を自動化します。"},
{"id": "doc-35", "title": "Amazon Cognito", "text": "Amazon Cognito は、Web およびモバイルアプリケーションの認証、認可、ユーザー管理を提供するサービスです。ソーシャル ID プロバイダーとの連携も可能です。"},
{"id": "doc-36", "title": "AWS AppSync", "text": "AWS AppSync は、GraphQL API を構築するフルマネージドサービスです。リアルタイムデータ同期やオフライン対応のアプリケーション開発を支援します。"},
{"id": "doc-37", "title": "Amazon MSK", "text": "Amazon MSK は、Apache Kafka 互換のフルマネージドストリーミングサービスです。Kafka クラスターの運用負荷を軽減し、ストリーミングアプリケーションを構築できます。"},
{"id": "doc-38", "title": "AWS Amplify", "text": "AWS Amplify は、フルスタックの Web・モバイルアプリケーションを迅速に構築、デプロイできる開発プラットフォームです。ホスティングやバックエンド構築を統合的に支援します。"},
{"id": "doc-39", "title": "Amazon Neptune", "text": "Amazon Neptune は、フルマネージドのグラフデータベースサービスです。ソーシャルネットワークやレコメンデーションなど、関係性の分析に適しています。Neptune Analytics ではベクトル検索もサポートしています。"},
{"id": "doc-40", "title": "Amazon S3 Vectors", "text": "Amazon S3 Vectors は、ベクトルデータの保存とクエリをネイティブにサポートする S3 の機能です。専用のベクトルデータベースを運用せずに、低コストでベクトル検索を実現します。"}
]
This is a script that converts these documents into embedding vectors using Titan Text Embeddings V2 and saves them as a LanceDB table on S3. The number of vector dimensions (1024) and type (float32) are fixed using FixedSizeList in the PyArrow schema.
01_setup_table.py
import json
import os
import time
import boto3
import lancedb
import pyarrow as pa
REGION = "ap-northeast-1"
DB_URI = os.environ["LANCEDB_URI"] # Example: s3://lancedb-blog-xxxx/db
EMBED_MODEL_ID = "amazon.titan-embed-text-v2:0"
bedrock = boto3.client("bedrock-runtime", region_name=REGION)
def embed(text: str) -> list[float]:
body = json.dumps({"inputText": text, "dimensions": 1024, "normalize": True})
res = bedrock.invoke_model(modelId=EMBED_MODEL_ID, body=body)
return json.loads(res["body"].read())["embedding"]
def main():
with open("sample_docs.json") as f:
docs = json.load(f)
print(f"Generating embeddings: {len(docs)} items")
start = time.perf_counter()
data = [
{
"id": doc["id"],
"title": doc["title"],
"text": doc["text"],
"vector": embed(doc["text"]),
}
for doc in docs
]
print(f"Embedding generation complete: {time.perf_counter() - start:.1f} seconds")
schema = pa.schema(
[
pa.field("id", pa.string()),
pa.field("title", pa.string()),
pa.field("text", pa.string()),
pa.field("vector", pa.list_(pa.float32(), 1024)),
]
)
db = lancedb.connect(DB_URI, storage_options={"region": REGION})
start = time.perf_counter()
table = db.create_table("aws_services", data=data, schema=schema, mode="overwrite")
print(f"Table creation complete: {time.perf_counter() - start:.1f} seconds")
print(f"Registered rows: {table.count_rows()}")
if __name__ == "__main__":
main()
Run it.
% export LANCEDB_URI="s3://cm-lancedb-blog-20260720/db"
% uv run python 01_setup_table.py
Generating embeddings: 40 items
Embedding generation complete: 6.0 seconds
[2026-07-20T13:58:44Z WARN lance::dataset::write::insert] No existing dataset at s3://cm-lancedb-blog-20260720/db/aws_services.lance, it will be created
Table creation complete: 0.4 seconds
Registered rows: 40
Embedding generation for 40 items completed in 6.6 seconds, and table creation on S3 completed in 0.4 seconds. What is notable about authentication is that only the region was specified in storage_options. Without explicitly passing access keys, the same default credential chain as the AWS CLI was used as-is. Note that omitting the region specification may cause a connection error due to a mismatch with the bucket's region, so it is safer to specify it explicitly.
Running vector search
Let's search the created table using a natural language query in Japanese. The embed() function for embedding generation is the same as in the previous script.
02_search.py
"""Run vector search (without index) against a LanceDB table on S3"""
import json
import os
import time
import boto3
import lancedb
REGION = "ap-northeast-1"
DB_URI = os.environ["LANCEDB_URI"]
EMBED_MODEL_ID = "amazon.titan-embed-text-v2:0"
bedrock = boto3.client("bedrock-runtime", region_name=REGION)
def embed(text: str) -> list[float]:
body = json.dumps({"inputText": text, "dimensions": 1024, "normalize": True})
res = bedrock.invoke_model(modelId=EMBED_MODEL_ID, body=body)
return json.loads(res["body"].read())["embedding"]
def main():
db = lancedb.connect(DB_URI, storage_options={"region": REGION})
table = db.open_table("aws_services")
queries = [
"I want to run containers without managing servers",
"I want to collect and analyze logs, and monitor",
"What services support vector search?",
]
for query in queries:
qvec = embed(query)
start = time.perf_counter()
results = table.search(qvec).limit(3).to_pandas()
elapsed = (time.perf_counter() - start) * 1000
print(f"\nQuery: {query} (Search time: {elapsed:.0f} ms *reference value)")
for _, row in results.iterrows():
print(f" {row['_distance']:.4f} {row['title']}: {row['text'][:45]}...")
if __name__ == "__main__":
main()
% export LANCEDB_URI="s3://cm-lancedb-blog-20260720/db"
% uv run python 02_search.py
クエリ: サーバーを管理せずにコンテナを実行したい(検索時間: 482 ms ※参考値)
1.4603 Amazon ECS: Amazon ECS は、コンテナ化されたアプリケーションを実行するフルマネージドなコンテ...
1.4612 AWS Fargate: AWS Fargate は、サーバーを管理せずにコンテナを実行できるサーバーレスコンピュー...
1.5624 Amazon EKS: Amazon EKS は、マネージドな Kubernetes サービスです。Kuberne...
クエリ: ログを集めて分析、監視したい(検索時間: 165 ms ※参考値)
1.4368 Amazon CloudWatch: Amazon CloudWatch は、AWS リソースとアプリケーションのモニタリングサ...
1.6223 Amazon OpenSearch Service: Amazon OpenSearch Service は、ログ分析や全文検索、ベクトル検索を...
1.7032 AWS Amplify: AWS Amplify は、フルスタックの Web・モバイルアプリケーションを迅速に構築、...
クエリ: ベクトル検索ができるサービスは?(検索時間: 179 ms ※参考値)
1.2319 Amazon OpenSearch Service: Amazon OpenSearch Service は、ログ分析や全文検索、ベクトル検索を...
1.2856 Amazon S3 Vectors: Amazon S3 Vectors は、ベクトルデータの保存とクエリをネイティブにサポート...
1.3629 Amazon Neptune: Amazon Neptune は、フルマネージドのグラフデータベースサービスです。ソーシャ...
Semantically appropriate services were returned at the top for all three queries. The fact that ECS, Fargate, and EKS appear for "running containers without managing servers" shows that semantic search works well even in Japanese. The search time was 482 ms for the first query and 165–179 ms for subsequent ones. Since this is accessing S3 from a local PC, these values are reference values that depend on the network environment.
Note that since no index has been created for this search, it is an exact nearest-neighbor search using a full scan (brute-force). _distance is the L2 distance, and since normalize: true was specified during embedding generation, the ranking is equivalent to cosine similarity.
Checking the file layout on S3
Let's see what LanceDB wrote to S3.
% aws s3 ls s3://cm-lancedb-blog-20260720/db/ --recursive --human-readable
2026-07-20 22:58:45 309 Bytes db/__manifest/_transactions/0-1d216e38-0365-48cf-84e5-c032643b3a4d.txn
2026-07-20 22:58:45 692 Bytes db/__manifest/_versions/18446744073709551614.manifest
2026-07-20 22:58:45 277 Bytes db/aws_services.lance/_transactions/0-02fc41eb-5e06-49a4-86df-da4b74c79914.txn
2026-07-20 22:58:45 631 Bytes db/aws_services.lance/_versions/18446744073709551614.manifest
2026-07-20 22:58:45 170.8 KiB db/aws_services.lance/data/1000001001110000101011001c253343bc89df2f778dc015a3.lance
The table with 40 rows and 1024 dimensions consisted of just 5 objects totaling approximately 173 KB. The structure has the actual data in .lance files under data/, manifests managing table versions under _versions/, and transaction logs placed under _transactions/. Changes to the table are designed to accumulate as versions, and from the file layout, it is clear that LanceDB itself has version management.
Trying IVF_PQ index creation
The searches up to this point were full scans without an index, but as the number of data items increases, search time grows proportionally. Therefore, for large-scale data, it is common to use ANN (Approximate Nearest Neighbor) indexes, which trade a small amount of accuracy for speed. The default index type in LanceDB is IVF_PQ, which combines the following two methods.
- IVF (Inverted File Index): Clusters all vectors using k-means, and during search, only scans the clusters closest to the query to narrow down the search range. The
num_partitionsin the code below corresponds to the number of clusters - PQ (Product Quantization): Divides each vector into multiple sub-vectors and replaces each with a small number of representative points for compression. This greatly reduces memory usage and the cost of distance computation.
num_sub_vectorscorresponds to the number of divisions
This time the data is only 40 rows, but let's try creating an index anyway.
03_index.py
import os
import lancedb
REGION = "ap-northeast-1"
DB_URI = os.environ["LANCEDB_URI"]
def main():
db = lancedb.connect(DB_URI, storage_options={"region": REGION})
table = db.open_table("aws_services")
print(f"Row count: {table.count_rows()}")
try:
table.create_index(metric="l2", num_partitions=4, num_sub_vectors=16)
print("Index creation: Success")
print(table.list_indices())
except Exception as e:
print(f"Index creation: Failed ({type(e).__name__})")
print(e)
if __name__ == "__main__":
main()
The following error was returned.
% export LANCEDB_URI="s3://cm-lancedb-blog-20260720/db"
uv run python 03_index.py
Row count: 40
/Users/ishikawa.satoru/workspaces/cc/blog/20260720-lancedb-on-aws/03_index.py:16: DeprecationWarning: The create_index() API with metric/num_partitions parameters is deprecated and will be removed in a future version. Please migrate to the new unified API:
# Old (deprecated):
table.create_index('l2', vector_column_name='my_vector')
# New (recommended):
table.create_index('my_vector', config=IvfPq(distance_type='l2'))
table.create_index(metric="l2", num_partitions=4, num_sub_vectors=16)
Index creation: Failed (RuntimeError)
lance error: Unprocessable: Not enough rows to train PQ. Requires 256 rows but only 40 available, /Users/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lance-index-8.0.0/src/vector/pq/builder.rs:179:24
PQ (Product Quantization) training requires a minimum of 256 rows. For small amounts of data, it is correct to skip creating an index and rely on a full scan; in fact, as mentioned earlier, 40 rows can be searched in around 130 ms. It seems appropriate to consider creating an index only after the data exceeds tens of thousands of rows.
Also, the following DeprecationWarning was displayed during execution. In lancedb 0.34.0, passing metric and num_partitions directly is deprecated, and migration to a unified API that passes an IvfPq object to config is underway.
DeprecationWarning: The create_index() API with metric/num_partitions parameters is deprecated
# New (recommended):
table.create_index('my_vector', config=IvfPq(distance_type='l2'))
Trying a Simple RAG with LanceDB + Bedrock (Claude Haiku 4.5)
Finally, let's run a simple RAG that passes vector search results as context to Claude Haiku 4.5.
04_rag.py
import json
import os
import boto3
import lancedb
REGION = "ap-northeast-1"
DB_URI = os.environ["LANCEDB_URI"]
EMBED_MODEL_ID = "amazon.titan-embed-text-v2:0"
CHAT_MODEL_ID = "jp.anthropic.claude-haiku-4-5-20251001-v1:0"
bedrock = boto3.client("bedrock-runtime", region_name=REGION)
def embed(text: str) -> list[float]:
body = json.dumps({"inputText": text, "dimensions": 1024, "normalize": True})
res = bedrock.invoke_model(modelId=EMBED_MODEL_ID, body=body)
return json.loads(res["body"].read())["embedding"]
def main():
db = lancedb.connect(DB_URI, storage_options={"region": REGION})
table = db.open_table("aws_services")
question = "コストを抑えてベクトル検索を実現できる AWS のサービスや構成を教えてください"
results = table.search(embed(question)).limit(3).to_pandas()
context = "\n\n".join(
f"【{row['title']}】{row['text']}" for _, row in results.iterrows()
)
prompt = (
"以下の参考ドキュメントの内容に基づいて質問に回答してください。\n"
"参考ドキュメントに記載のない情報は推測せず、その旨を明記してください。\n\n"
f"# 参考ドキュメント\n{context}\n\n# 質問\n{question}"
)
res = bedrock.converse(
modelId=CHAT_MODEL_ID,
messages=[{"role": "user", "content": [{"text": prompt}]}],
inferenceConfig={"maxTokens": 600, "temperature": 0},
)
print(f"質問: {question}\n")
print("検索でヒットしたドキュメント:")
for _, row in results.iterrows():
print(f" - {row['title']} (distance: {row['_distance']:.4f})")
print("\n回答:")
print(res["output"]["message"]["content"][0]["text"])
usage = res["usage"]
print(f"\ntokens: input={usage['inputTokens']}, output={usage['outputTokens']}")
if __name__ == "__main__":
main()
% export LANCEDB_URI="s3://cm-lancedb-blog-20260720/db"
% uv run python 04_rag.py
質問: コストを抑えてベクトル検索を実現できる AWS のサービスや構成を教えてください
検索でヒットしたドキュメント:
- Amazon S3 Vectors (distance: 1.1685)
- Amazon OpenSearch Service (distance: 1.2089)
- AWS Lambda (distance: 1.2487)
回答:
# 回答
参考ドキュメントに基づいて、コストを抑えてベクトル検索を実現できるAWSのサービスは以下の通りです。
## **Amazon S3 Vectors**
- **最もコスト効率的な選択肢**です
- ベクトルデータの保存とクエリをネイティブにサポート
- 専用のベクトルデータベースを運用する必要がないため、低コストでベクトル検索を実現できます
## **Amazon OpenSearch Service**
- ベクトル検索に対応しており、k-NN検索によるセマンティック検索が可能です
- ただし、マネージドサービスであるため、Amazon S3 Vectorsと比較してコスト面での優位性については、参考ドキュメントに記載されていません
## **推奨構成**
参考ドキュメントの情報のみに基づくと、**Amazon S3 Vectorsの利用が最もコスト効率的**と言えます。
---
**注記:** AWS Lambda との組み合わせなど、より詳細な構成提案については、参考ドキュメントに具体的な記載がないため、推測での回答は控えさせていただきます。
tokens: input=338, output=359
The complete RAG flow — question embedding → similarity search with LanceDB → answer generation using the top 3 results as context — worked without any issues. The answer also faithfully follows the instruction to "not speculate on information not found in the reference documents."
Analysis
Here is a summary of the insights gained from actually trying this out.
- Very easy to get started: The only AWS resource needed is a single S3 bucket. Simply run
pip install lancedband set the connection string tos3://, and the same code used in local development runs as-is with an S3 backend. Authentication uses the default credential chain, so there is no need to write access keys in the code - It's safer to explicitly specify the region: By explicitly specifying the bucket's region, such as
storage_options={"region": "ap-northeast-1"}, you can avoid connection errors caused by region mismatches - No index needed for small datasets: The IVF_PQ index requires a minimum of 256 rows for PQ training. With the 40 records used this time, a full scan completed in around 130 ms (for reference), so index consideration can wait until the data exceeds tens of thousands of records
- Good storage efficiency: 40 records at 1024 dimensions resulted in approximately 173 KB across 5 objects. Given S3's storage pricing, the cost of storing vectors is minimal. Throughout this entire verification, Bedrock calls totaled only a few thousand tokens, and S3 requests totaled only a few dozen
- Concurrent write support has improved: Previously, using DynamoDB as a commit store alongside the S3 backend was required for concurrent writes from multiple processes, but the official documentation now states that concurrent writes can be handled without an external coordinator, thanks to S3's atomic write support
- Japanese is also practical: Titan Text Embeddings V2 is optimized for English, and Japanese is among 100+ languages listed as preview, but the combination of Japanese documents and Japanese queries in this test still yielded reasonable search results
The following are also things worth trying in the future.
- Searching from AWS Lambda (packaging the lancedb package in a container image)
- IVF_PQ index creation and search performance with data in the hundreds of thousands of records
- Latency improvements when using S3 Express One Zone as the backend
Comparing LanceDB and Amazon S3 Vectors
As another option for "storing vectors in S3," it's also worth considering how to differentiate between Amazon S3 Vectors and LanceDB. Here is a summary of the differences between the two.
| Item | LanceDB (S3 backend) | Amazon S3 Vectors |
|---|---|---|
| Delivery model | Embedded OSS library | Native S3 feature (fully managed) |
| Where search processing runs | Inside the application process | AWS side (QueryVectors API) |
| Index | Create and manage IVF_PQ, etc. yourself | Automatically managed (no creation or tuning required) |
| Scale | Depends on design (case study of 3.5 billion records exists) | Up to 2 billion vectors / index |
| Search features | Vector search + full-text search + SQL filter | Vector search + metadata filter |
| Distance metrics | L2 / cosine / dot | cosine / euclidean |
| Table version control | Available (rollback to past versions possible) | Not available |
| Cost | S3 storage + request fees only | Storage + PUT + query pay-as-you-go |
| Ecosystem | OSS integrations such as LangChain / LlamaIndex | Bedrock Knowledge Bases / OpenSearch integration |
| Local development | Same code also works with local filesystem | AWS only |
The major difference is where search processing runs. S3 Vectors is a fully managed feature where AWS handles both storing and searching vectors, while LanceDB uses S3 only as storage and performs search computation on the application side. S3 Vectors is the better fit when you want fully managed, hassle-free scaling, or when you want to use it as a vector store for Bedrock Knowledge Bases.
Benefits of Choosing LanceDB
On the other hand, adopting LanceDB offers the following benefits.
- Development experience where the same code as local runs on S3: As demonstrated in this verification, simply changing the connection string to
s3://allows local development code to run as-is. Unit tests can also be completed entirely on the local filesystem - Costs are easy to predict: There are no query fees, and the only costs incurred are the actual S3 storage and request fees. Even with 40 records at 1024 dimensions in this test, it was approximately 173 KB
- Rich database features: In addition to vector search, full-text search, SQL-syntax metadata filters, and automatic versioning (time travel) can all be used within a single library
- Low lock-in: Since it is OSS, the same code also works with local, Google Cloud Storage, and Azure Blob Storage, making environment migration easy
- Can be embedded in serverless architectures: By embedding it in AWS Lambda, you can build a search API without a DB server. The Metagenomi case study of 3.5 billion records mentioned at the beginning also uses this configuration
Conversely, because of the architecture where search runs on the application side, you need to design for scaling concurrent access and for creating and updating IVF_PQ indexes yourself. The likely division of use cases is: LanceDB when you want to start small and keep the configuration simple, or when you want to embed the search functionality within the application itself; and S3 Vectors when you want to manage large-scale vectors in a fully managed way.
In Closing
LanceDB's S3 backend made it possible to realize a DB-serverless vector search infrastructure with the simple steps of "create an S3 bucket and change the connection string." The combination with Bedrock's embedding model is also fully self-contained with just boto3, and this configuration is more than sufficient for a small-scale RAG. For those who want to reduce the operational costs of a vector database, or those who want to start small with vector search, why not consider it as one of your options?
