ストーリー
ベクトルDBの分類
3つのカテゴリ
graph TD
Title["ベクトルDB の分類"]
Title --- A & B & C
A["専用ベクトルDB<br/>(Purpose-built)"]
AList["Pinecone, Qdrant, Weaviate,<br/>Milvus, ChromaDB"]
A --- AList
B["既存DBの拡張<br/>(Extension)"]
BList["pgvector(PostgreSQL),<br/>MongoDB Atlas Vector Search,<br/>Elasticsearch kNN"]
B --- BList
C["インメモリ/<br/>ライブラリ"]
CList["FAISS, Annoy, hnswlib<br/>(DB機能なし、検索ライブラリのみ)"]
C --- CList
classDef category fill:#e0f2fe,stroke:#0284c7,font-weight:bold
classDef items fill:#f0fdf4,stroke:#22c55e
classDef title fill:#1e40af,stroke:#1e40af,color:#fff,font-weight:bold
class A,B,C category
class AList,BList,CList items
class Title title
主要ベクトルDBの詳細比較
Pinecone
graph TD
PC["Pinecone
マネージドSaaS(専用ベクトルDB)"]
PC --> F["特徴"]
F --- F1["フルマネージド
インフラ管理不要"]
F --- F2["サーバーレスプランで
小規模開始可能"]
F --- F3["メタデータフィルタリング対応"]
F --- F4["Namespace で
マルチテナント対応"]
PC --> P["長所"]
P --- P1["運用負荷ゼロ"]
P --- P2["スケーラビリティが高い"]
P --- P3["高可用性
99.95% SLA"]
PC --> C["短所"]
C --- C1["ベンダーロックイン"]
C --- C2["データが外部に保存される"]
C --- C3["大規模ではコスト高"]
PC --> PR["価格: Serverless
$0.33/1M reads, $2/1M writes"]
style PC fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e40af
style F fill:#d1fae5,stroke:#059669,color:#065f46
style P fill:#d1fae5,stroke:#059669,color:#065f46
style C fill:#fee2e2,stroke:#dc2626,color:#991b1b
style PR fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#92400e
style F1,F2,F3,F4,P1,P2,P3,C1,C2,C3 fill:#f3f4f6,stroke:#9ca3af,color:#374151
Qdrant
graph TD
QD["Qdrant
オープンソース(専用ベクトルDB)"]
QD --> F["特徴"]
F --- F1["Rust製で高速"]
F --- F2["豊富なフィルタリング機能"]
F --- F3["ペイロード(メタデータ)に対する
高度なクエリ"]
F --- F4["Qdrant Cloud(マネージド)
も利用可能"]
QD --> P["長所"]
P --- P1["高い検索パフォーマンス"]
P --- P2["セルフホスト可能
データ管理自由"]
P --- P3["gRPC & REST API"]
P --- P4["多彩な距離メトリクス"]
QD --> C["短所"]
C --- C1["セルフホスト時の運用負荷"]
C --- C2["エコシステムは
Pineconeほど成熟していない"]
QD --> PR["価格: OSS無料
Cloud $0.045/hr〜"]
style QD fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e40af
style F fill:#d1fae5,stroke:#059669,color:#065f46
style P fill:#d1fae5,stroke:#059669,color:#065f46
style C fill:#fee2e2,stroke:#dc2626,color:#991b1b
style PR fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#92400e
style F1,F2,F3,F4,P1,P2,P3,P4,C1,C2 fill:#f3f4f6,stroke:#9ca3af,color:#374151
Weaviate
graph TD
WV["Weaviate
オープンソース(専用ベクトルDB)"]
WV --> F["特徴"]
F --- F1["GraphQL APIでのクエリ"]
F --- F2["内蔵のベクタライズモジュール"]
F --- F3["ハイブリッド検索
BM25 + ベクトル のネイティブサポート"]
F --- F4["マルチモーダル対応"]
WV --> P["長所"]
P --- P1["ハイブリッド検索が
簡単に使える"]
P --- P2["スキーマ定義による
データ管理"]
P --- P3["活発なコミュニティ"]
P --- P4["モジュラーアーキテクチャ"]
WV --> C["短所"]
C --- C1["メモリ使用量が大きい"]
C --- C2["GraphQL の学習コスト"]
WV --> PR["価格: OSS無料
Cloud $25/mo〜"]
style WV fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e40af
style F fill:#d1fae5,stroke:#059669,color:#065f46
style P fill:#d1fae5,stroke:#059669,color:#065f46
style C fill:#fee2e2,stroke:#dc2626,color:#991b1b
style PR fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#92400e
style F1,F2,F3,F4,P1,P2,P3,P4,C1,C2 fill:#f3f4f6,stroke:#9ca3af,color:#374151
pgvector (PostgreSQL)
graph TD
PG["pgvector
PostgreSQL拡張"]
PG --> F["特徴"]
F --- F1["既存のPostgreSQLに追加可能"]
F --- F2["SQLでベクトル検索が可能"]
F --- F3["HNSW & IVFFlat インデックス"]
F --- F4["トランザクション対応"]
PG --> P["長所"]
P --- P1["既存インフラの活用"]
P --- P2["SQLの知識がそのまま使える"]
P --- P3["メタデータとの結合が容易"]
P --- P4["ACID トランザクション"]
P --- P5["低い運用学習コスト"]
PG --> C["短所"]
C --- C1["大規模データでの
スケーラビリティ"]
C --- C2["専用DBほどの
検索パフォーマンスは出ない"]
C --- C3["分散構成が難しい"]
PG --> PR["価格: PostgreSQLと同じ
拡張は無料"]
style PG fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e40af
style F fill:#d1fae5,stroke:#059669,color:#065f46
style P fill:#d1fae5,stroke:#059669,color:#065f46
style C fill:#fee2e2,stroke:#dc2626,color:#991b1b
style PR fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#92400e
style F1,F2,F3,F4,P1,P2,P3,P4,P5,C1,C2,C3 fill:#f3f4f6,stroke:#9ca3af,color:#374151
選定マトリクス
ユースケース別推奨
| ユースケース | 推奨 | 理由 |
|---|---|---|
| MVP/PoC | ChromaDB / pgvector | 最速でプロトタイプ構築 |
| 既にPostgreSQL運用中 | pgvector | インフラ追加不要 |
| 本番RAG (〜100万ベクトル) | pgvector / Qdrant | コスパ良好 |
| 本番RAG (100万〜1億) | Qdrant / Pinecone / Weaviate | スケーラビリティ必要 |
| ハイブリッド検索重視 | Weaviate | ネイティブサポート |
| 運用負荷最小 | Pinecone | フルマネージド |
| データ主権が最重要 | Qdrant (self-hosted) / pgvector | 完全自社管理 |
機能比較表
| 機能 | Pinecone | Qdrant | Weaviate | pgvector |
|---|---|---|---|---|
| ハイブリッド検索 | Sparse vector | BM25プラグイン | ネイティブ | 別途全文検索 |
| メタデータフィルタ | 対応 | 高度 | GraphQL | SQL WHERE |
| マルチテナント | Namespace | コレクション | テナント | スキーマ/RLS |
| バックアップ | 自動 | スナップショット | バックアップ | pg_dump |
| レプリケーション | 自動 | Raft | 自動 | PostgreSQL方式 |
| 最大ベクトル数 | 10億+ | 数十億 | 数億 | 数千万(推奨) |
TypeScript実装例
pgvector を使った実装
import { Pool } from 'pg';
class PgVectorStore implements VectorStore {
constructor(private readonly pool: Pool) {}
async initialize(): Promise<void> {
await this.pool.query('CREATE EXTENSION IF NOT EXISTS vector');
await this.pool.query(`
CREATE TABLE IF NOT EXISTS embeddings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
content TEXT NOT NULL,
embedding vector(1536),
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
)
`);
// HNSW インデックス作成
await this.pool.query(`
CREATE INDEX IF NOT EXISTS embeddings_hnsw_idx
ON embeddings USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200)
`);
}
async upsert(chunks: Chunk[]): Promise<void> {
const query = `
INSERT INTO embeddings (id, content, embedding, metadata)
VALUES ($1, $2, $3::vector, $4::jsonb)
ON CONFLICT (id) DO UPDATE SET
content = EXCLUDED.content,
embedding = EXCLUDED.embedding,
metadata = EXCLUDED.metadata
`;
for (const chunk of chunks) {
await this.pool.query(query, [
chunk.id,
chunk.content,
`[${chunk.embedding!.join(',')}]`,
JSON.stringify(chunk.metadata),
]);
}
}
async search(
queryEmbedding: number[],
topK: number,
filter?: Record<string, unknown>,
): Promise<RetrievedContext[]> {
let whereClause = '';
const params: unknown[] = [
`[${queryEmbedding.join(',')}]`,
topK,
];
if (filter) {
const conditions: string[] = [];
let paramIndex = 3;
for (const [key, value] of Object.entries(filter)) {
conditions.push(`metadata->>'${key}' = $${paramIndex}`);
params.push(value);
paramIndex++;
}
if (conditions.length > 0) {
whereClause = `WHERE ${conditions.join(' AND ')}`;
}
}
const result = await this.pool.query(
`SELECT id, content, metadata,
1 - (embedding <=> $1::vector) AS score
FROM embeddings
${whereClause}
ORDER BY embedding <=> $1::vector
LIMIT $2`,
params,
);
return result.rows.map(row => ({
chunk: {
id: row.id,
documentId: row.metadata.documentId,
content: row.content,
metadata: row.metadata,
},
score: row.score,
}));
}
}
Qdrant を使った実装
import { QdrantClient } from '@qdrant/js-client-rest';
class QdrantVectorStore implements VectorStore {
private client: QdrantClient;
private collectionName: string;
constructor(url: string, collectionName: string) {
this.client = new QdrantClient({ url });
this.collectionName = collectionName;
}
async initialize(): Promise<void> {
await this.client.createCollection(this.collectionName, {
vectors: {
size: 1536,
distance: 'Cosine',
},
optimizers_config: {
default_segment_number: 2,
},
replication_factor: 1,
});
}
async upsert(chunks: Chunk[]): Promise<void> {
const points = chunks.map(chunk => ({
id: chunk.id,
vector: chunk.embedding!,
payload: {
content: chunk.content,
...chunk.metadata,
},
}));
await this.client.upsert(this.collectionName, {
wait: true,
points,
});
}
async search(
queryEmbedding: number[],
topK: number,
filter?: Record<string, unknown>,
): Promise<RetrievedContext[]> {
const qdrantFilter = filter ? this.buildFilter(filter) : undefined;
const results = await this.client.search(this.collectionName, {
vector: queryEmbedding,
limit: topK,
filter: qdrantFilter,
with_payload: true,
});
return results.map(result => ({
chunk: {
id: String(result.id),
documentId: String(result.payload?.documentId ?? ''),
content: String(result.payload?.content ?? ''),
metadata: result.payload as Record<string, unknown>,
},
score: result.score,
}));
}
private buildFilter(filter: Record<string, unknown>) {
const must = Object.entries(filter).map(([key, value]) => ({
key,
match: { value },
}));
return { must };
}
}
VectorStoreのインターフェース定義
interface VectorStore {
initialize(): Promise<void>;
upsert(chunks: Chunk[]): Promise<void>;
search(
queryEmbedding: number[],
topK: number,
filter?: Record<string, unknown>,
): Promise<RetrievedContext[]>;
delete(ids: string[]): Promise<void>;
}
// ファクトリパターンで切り替え可能にする
function createVectorStore(config: VectorStoreConfig): VectorStore {
switch (config.type) {
case 'pgvector':
return new PgVectorStore(new Pool(config.connection));
case 'qdrant':
return new QdrantVectorStore(config.url, config.collection);
case 'pinecone':
return new PineconeVectorStore(config.apiKey, config.index);
default:
throw new Error(`Unknown vector store type: ${config.type}`);
}
}
まとめ
| ポイント | 内容 |
|---|---|
| 3つのカテゴリ | 専用ベクトルDB / 既存DB拡張 / インメモリライブラリ |
| pgvector | 既存PostgreSQL環境なら最も低コスト。数千万ベクトルまで |
| Qdrant | 高性能セルフホスト。データ主権が必要な場合に最適 |
| Pinecone | フルマネージドで運用負荷最小。コストは高め |
| 選定基準 | データ量、運用負荷、データ主権、既存インフラとの親和性 |
チェックリスト
- ベクトルDBの3つのカテゴリを理解した
- 主要ベクトルDB(Pinecone/Qdrant/Weaviate/pgvector)の特徴を把握した
- ユースケース別の選定基準を理解した
- TypeScriptでの実装パターンを理解した
次のステップへ
ベクトルDBの選定基準を学びました。次のセクションでは、ベクトルDBの検索性能を左右するインデックス戦略について深く掘り下げます。
ツールの選定は、要件を明確にしてから。技術の流行ではなく、ユースケースで判断すること。
推定読了時間: 40分