ブログ記事

Vector Database実践入門2025 - AI時代のデータベース完全ガイド

Pinecone、Weaviate、Qdrant、ChromaDBを徹底比較。実践的なRAG実装、セマンティック検索、埋め込みモデルの選定から本番運用まで、ベクトルデータベースのすべてを解説します。

AI・機械学習
Vector Database RAG AI セマンティック検索 embeddings
Vector Database実践入門2025 - AI時代のデータベース完全ガイドのヒーロー画像

2025 年、AIアプリケーションの心臓部となった Vector Database。ChatGPT のような ai アシスタント、画像検索システム、レコメンドエンジン。これらすべての裏側で、高次元ベクトル空間での類似性検索が動いています。

本記事では、主要 4 プラットフォーム(Pinecone、Weaviate、Qdrant、ChromaDB)の徹底比較から、実践的なRAG実装10億ベクトルを扱うスケーリング戦略まで、Vector Database のすべてを解説します。

この記事で学べること

  • Vector Database の基礎概念と選定基準
  • 主要 4 プラットフォームの機能・性能・コスト比較
  • 実践的な RAG(Retrieval-Augmented Generation)実装
  • 埋め込みモデルの選定とチューニング
  • 本番環境でのスケーリングと最適化
  • セマンティック検索の実装パターン
  • ハイブリッド検索とマルチモーダル対応

目次

  1. Vector Database とは何か
  2. 主要プラットフォーム徹底比較
  3. 埋め込みモデルの選定と実装
  4. RAG 実装の完全ガイド
  5. セマンティック検索の構築
  6. スケーリングと最適化
  7. 本番運用のベストプラクティス
  8. 今後の展望とまとめ

Vector Databaseとは何か

なぜVector Databaseが必要なのか

従来のDBとVector DBの違い

チャートを読み込み中...

従来のデータベースは構造化データの完全一致検索に最適化されていますが、ai アプリケーションでは意味的な類似性を扱う必要があります。

Vector Databaseの核心技術

// ベクトルの基本概念
interface VectorEmbedding {
  // テキスト "犬が走る" を384次元のベクトルに変換
  text: string;
  vector: number[]; // [0.12, -0.34, 0.56, ..., 0.78] (384次元)
}

// 類似度計算の例
function cosineSimilarity(vec1: number[], vec2: number[]): number {
  const dotProduct = vec1.reduce((sum, val, i) => sum + val * vec2[i], 0);
  const norm1 = Math.sqrt(vec1.reduce((sum, val) => sum + val * val, 0));
  const norm2 = Math.sqrt(vec2.reduce((sum, val) => sum + val * val, 0));
  return dotProduct / (norm1 * norm2);
}

// 使用例
const dogRunning = [0.12, -0.34, 0.56, ...]; // "犬が走る"
const catJumping = [0.15, -0.31, 0.52, ...]; // "猫が跳ぶ"
const carDriving = [-0.82, 0.21, -0.15, ...]; // "車が走る"

console.log(cosineSimilarity(dogRunning, catJumping)); // 0.92 (高い類似度)
console.log(cosineSimilarity(dogRunning, carDriving)); // 0.31 (低い類似度)

主な用途

Vector Databaseの主な用途
用途 具体例 必要な機能
セマンティック検索 自然言語での文書検索 高速な近傍探索
RAG ChatGPTのような知識拡張 大規模ベクトル管理
画像検索 類似画像の検索 マルチモーダル対応
レコメンデーション 商品・コンテンツ推薦 リアルタイム更新
異常検知 正常パターンからの逸脱検出 クラスタリング
顔認識 顔の特徴ベクトル比較 高精度な距離計算

主要プラットフォーム徹底比較

2025年のVector Database市場

爆発的成長期

ChatGPTブームでVector DBへの注目急上昇

機能拡充期

ハイブリッド検索、マルチモーダル対応が標準化

成熟期突入

汎用DBもベクトル機能を追加

差別化競争

特化型DBが独自機能で勝負

統合の時代

AIプラットフォームとの深い統合へ

プラットフォーム詳細比較

選定のポイント

Pinecone: マネージドサービスで運用負荷を最小化したい場合 Weaviate: オープンソースで柔軟性を重視する場合 Qdrant: 高性能とリソース効率を最優先する場合 ChromaDB: LLM アプリケーションに特化した開発をする場合

1. Pinecone - フルマネージドの王者

// Pinecone実装例
import { PineconeClient } from '@pinecone-database/pinecone';

const pinecone = new PineconeClient();

async function initPinecone() {
  await pinecone.init({
    apiKey: process.env.PINECONE_API_KEY,
    environment: 'us-west4-gcp'
  });

  // インデックスの作成
  await pinecone.createIndex({
    name: 'my-rag-index',
    dimension: 1536, // OpenAI ada-002の次元数
    metric: 'cosine',
    pods: 1,
    replicas: 1,
    pod_type: 'p1.x1'
  });

  const index = pinecone.Index('my-rag-index');

  // ベクトルの挿入
  await index.upsert({
    vectors: [
      {
        id: 'doc1',
        values: [0.1, 0.2, ...], // 1536次元のベクトル
        metadata: {
          title: 'AI入門',
          content: '人工知能の基礎について...',
          source: 'textbook'
        }
      }
    ]
  });

  // 類似検索
  const queryResponse = await index.query({
    vector: [0.15, 0.25, ...], // クエリベクトル
    topK: 10,
    includeMetadata: true,
    filter: {
      source: { $eq: 'textbook' }
    }
  });
}

特徴

  • ✅ 完全マネージド(インフラ管理不要)
  • ✅ 自動スケーリング
  • ✅ SOC2 Type II、GDPR 準拠
  • ❌ SaaS のみ(オンプレミス不可)
  • ❌ コストが比較的高い

2. Weaviate - オープンソースの柔軟性

// Weaviate実装例
import weaviate from 'weaviate-ts-client';

const client = weaviate.client({
  scheme: 'http',
  host: 'localhost:8080',
});

async function setupWeaviate() {
  // スキーマの定義
  const schemaConfig = {
    class: 'Article',
    vectorizer: 'text2vec-openai',
    moduleConfig: {
      'text2vec-openai': {
        model: 'text-embedding-ada-002',
        type: 'text'
      }
    },
    properties: [
      {
        name: 'title',
        dataType: ['string'],
      },
      {
        name: 'content',
        dataType: ['text'],
      }
    ]
  };

  await client.schema.classCreator().withClass(schemaConfig).do();

  // データの追加(自動ベクトル化)
  await client.data.creator()
    .withClassName('Article')
    .withProperties({
      title: 'GraphQLの基礎',
      content: 'GraphQLは効率的なAPI設計を可能にします...'
    })
    .do();

  // ハイブリッド検索(ベクトル + キーワード)
  const result = await client.graphql
    .get()
    .withClassName('Article')
    .withHybrid({
      query: 'API設計の効率化',
      alpha: 0.75 // 0=キーワードのみ、1=ベクトルのみ
    })
    .withFields('title content _additional { score }')
    .do();
}

特徴

  • ✅ オープンソース(完全な制御)
  • ✅ graphql ネイティブ
  • ✅ モジュール拡張可能
  • ✅ ハイブリッド検索標準搭載
  • ❌ kubernetes でのスケーリングが複雑

3. Qdrant - Rust製の高性能エンジン

// Qdrant実装例(Rust)
use qdrant_client::prelude::*;
use qdrant_client::qdrant::vectors_config::Config;
use qdrant_client::qdrant::{CreateCollection, VectorParams, VectorsConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = QdrantClient::from_url("http://localhost:6334").build()?;

    // コレクションの作成
    client
        .create_collection(&CreateCollection {
            collection_name: "products".to_string(),
            vectors_config: Some(VectorsConfig {
                config: Some(Config::Params(VectorParams {
                    size: 768,
                    distance: Distance::Cosine.into(),
                    ..Default::default()
                })),
            }),
            ..Default::default()
        })
        .await?;

    // ポイントの挿入
    let points = vec![
        PointStruct::new(
            1,
            vec![0.05, 0.61, 0.76, ...], // 768次元
            json!({
                "name": "高性能ノートPC",
                "category": "electronics",
                "price": 150000
            })
        ),
    ];

    client
        .upsert_points("products", points, None)
        .await?;

    // フィルタリング付き検索
    let search_result = client
        .search_points(&SearchPoints {
            collection_name: "products".to_string(),
            vector: vec![0.11, 0.62, 0.77, ...],
            filter: Some(Filter::must([
                Condition::range("price", Range {
                    lt: Some(200000.0),
                    ..Default::default()
                }),
            ])),
            limit: 10,
            with_payload: Some(true),
            ..Default::default()
        })
        .await?;

    Ok(())
}

特徴

  • ✅ Rust 製で高性能・低メモリ
  • ✅ 豊富なフィルタリング機能
  • ✅ スカラー量子化でメモリ削減
  • ✅ Raft プロトコルで高可用性
  • ❌ エコシステムがまだ発展途上

4. ChromaDB - LLMアプリ特化型

# ChromaDB実装例
import chromadb
from chromadb.utils import embedding_functions

# クライアントの初期化
client = chromadb.PersistentClient(path="./chroma_db")

# OpenAI埋め込み関数の設定
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
    api_key="your-api-key",
    model_name="text-embedding-ada-002"
)

# コレクションの作成
collection = client.create_collection(
    name="knowledge_base",
    embedding_function=openai_ef,
    metadata={"hnsw:space": "cosine"}
)

# ドキュメントの追加
collection.add(
    documents=[
        "量子コンピュータは従来のコンピュータとは異なる原理で動作します",
        "機械学習は大量のデータからパターンを学習します",
        "ブロックチェーンは分散型台帳技術です"
    ],
    metadatas=[
        {"source": "quantum_textbook", "chapter": 1},
        {"source": "ml_guide", "chapter": 3},
        {"source": "blockchain_intro", "chapter": 1}
    ],
    ids=["doc1", "doc2", "doc3"]
)

# セマンティック検索
results = collection.query(
    query_texts=["量子技術について教えて"],
    n_results=2,
    where={"chapter": 1}
)

# LangChainとの統合
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings

vectorstore = Chroma(
    collection_name="knowledge_base",
    embedding_function=OpenAIEmbeddings(),
    persist_directory="./chroma_db"
)

# RAGチェーンの構築
from langchain.chains import RetrievalQA
from langchain.llms import OpenAI

qa_chain = RetrievalQA.from_chain_type(
    llm=OpenAI(),
    retriever=vectorstore.as_retriever(search_kwargs={"k": 3})
)

answer = qa_chain.run("量子コンピュータの応用分野は?")

特徴

  • ✅ LangChain 統合が簡単
  • ✅ 軽量で導入が容易
  • ✅ ローカル・クラウド両対応
  • ✅ 開発者フレンドリー
  • ❌ 大規模運用には向かない

パフォーマンス比較

Qdrant - クエリ速度 95 %
Pinecone - クエリ速度 85 %
Weaviate - クエリ速度 80 %
ChromaDB - クエリ速度 70 %
Vector Databaseパフォーマンス比較(2025年時点)
項目 Pinecone Weaviate Qdrant ChromaDB
最大ベクトル数 数十億 数十億 数十億 数百万
レイテンシ(p99) 50ms 100ms 10ms 200ms
メモリ効率
価格(100万ベクトル) $70/月 自己ホスト 自己ホスト 自己ホスト
セットアップ時間 5分 30分 20分 10分
スケーリング 自動 手動 手動 制限あり

埋め込みモデルの選定と実装

埋め込みモデルの選定基準

埋め込みモデル選定フロー

チャートを読み込み中...

主要埋め込みモデルの比較

// 埋め込みモデルの実装例
import { OpenAI } from 'openai';
import { HuggingFaceInference } from '@huggingface/inference';

class EmbeddingService {
  private openai: OpenAI;
  private hf: HuggingFaceInference;

  constructor() {
    this.openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
    this.hf = new HuggingFaceInference(process.env.HF_API_KEY);
  }

  // OpenAI text-embedding-ada-002
  async embedWithOpenAI(text: string): Promise<number[]> {
    const response = await this.openai.embeddings.create({
      model: 'text-embedding-ada-002',
      input: text,
    });
    return response.data[0].embedding; // 1536次元
  }

  // Sentence Transformers (all-MiniLM-L6-v2)
  async embedWithSentenceTransformers(text: string): Promise<number[]> {
    const response = await this.hf.featureExtraction({
      model: 'sentence-transformers/all-MiniLM-L6-v2',
      inputs: text,
    });
    return response as number[]; // 384次元
  }

  // 日本語特化モデル
  async embedJapanese(text: string): Promise<number[]> {
    const response = await this.hf.featureExtraction({
      model: 'sonoisa/sentence-bert-base-ja-mean-tokens-v2',
      inputs: text,
    });
    return response as number[];
  }

  // バッチ処理での最適化
  async embedBatch(texts: string[], model: string = 'ada-002'): Promise<number[][]> {
    const batchSize = 100;
    const embeddings: number[][] = [];

    for (let i = 0; i < texts.length; i += batchSize) {
      const batch = texts.slice(i, i + batchSize);
      
      if (model === 'ada-002') {
        const response = await this.openai.embeddings.create({
          model: 'text-embedding-ada-002',
          input: batch,
        });
        embeddings.push(...response.data.map(d => d.embedding));
      }
      
      // レート制限対策
      await new Promise(resolve => setTimeout(resolve, 100));
    }

    return embeddings;
  }
}

埋め込みモデルの評価

MTEB (Massive Text Embedding Benchmark) スコア

埋め込みモデルの性能は、MTEB リーダーボードで評価されます:

  • 検索精度(Retrieval): 関連文書を正確に見つける能力
  • 意味的類似性(STS): テキスト間の意味的な近さを測る能力
  • 分類性能(Classification): カテゴリ分けの精度
// 埋め込みの品質評価
class EmbeddingEvaluator {
  // コサイン類似度の分布を可視化
  async evaluateEmbeddingQuality(
    embeddings: number[][],
    labels: string[]
  ): Promise<EvaluationResult> {
    const similarities: number[][] = [];
    
    // 全ペアの類似度計算
    for (let i = 0; i < embeddings.length; i++) {
      similarities[i] = [];
      for (let j = 0; j < embeddings.length; j++) {
        similarities[i][j] = this.cosineSimilarity(
          embeddings[i], 
          embeddings[j]
        );
      }
    }

    // 同一カテゴリ内の平均類似度
    const intraClassSimilarity = this.calculateIntraClassSimilarity(
      similarities, 
      labels
    );

    // 異なるカテゴリ間の平均類似度
    const interClassSimilarity = this.calculateInterClassSimilarity(
      similarities, 
      labels
    );

    return {
      separation: intraClassSimilarity - interClassSimilarity,
      intraClass: intraClassSimilarity,
      interClass: interClassSimilarity,
      recommendation: this.getRecommendation(
        intraClassSimilarity - interClassSimilarity
      )
    };
  }

  private getRecommendation(separation: number): string {
    if (separation > 0.5) return "優秀:そのまま使用可能";
    if (separation > 0.3) return "良好:用途によっては使用可能";
    if (separation > 0.1) return "要改善:ファインチューニング推奨";
    return "不適切:別のモデルを検討";
  }
}

RAG実装の完全ガイド

RAGアーキテクチャ

RAGシステムの全体像

チャートを読み込み中...

実践的なRAG実装

// 完全なRAGシステムの実装
import { OpenAI } from 'openai';
import { QdrantClient } from '@qdrant/js-client-rest';
import { Document } from 'langchain/document';
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';

class RAGSystem {
  private openai: OpenAI;
  private vectorDB: QdrantClient;
  private collectionName = 'knowledge_base';

  constructor() {
    this.openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
    this.vectorDB = new QdrantClient({ url: 'http://localhost:6333' });
  }

  // ステップ1: ドキュメントの前処理とチャンキング
  async preprocessDocuments(documents: string[]): Promise<Document[]> {
    const splitter = new RecursiveCharacterTextSplitter({
      chunkSize: 1000,
      chunkOverlap: 200,
      separators: ['\n\n', '\n', '。', '、', ' ', '']
    });

    const chunks: Document[] = [];
    
    for (const doc of documents) {
      const splitDocs = await splitter.createDocuments([doc]);
      chunks.push(...splitDocs);
    }

    return chunks;
  }

  // ステップ2: チャンクの埋め込みとインデックス作成
  async indexDocuments(chunks: Document[]): Promise<void> {
    const points = [];

    for (let i = 0; i < chunks.length; i++) {
      const embedding = await this.createEmbedding(chunks[i].pageContent);
      
      points.push({
        id: i,
        vector: embedding,
        payload: {
          text: chunks[i].pageContent,
          metadata: chunks[i].metadata
        }
      });
    }

    // バッチでVector DBに挿入
    await this.vectorDB.upsert(this.collectionName, {
      wait: true,
      points
    });
  }

  // ステップ3: 検索拡張生成
  async generate(query: string, options: RAGOptions = {}): Promise<RAGResponse> {
    const {
      topK = 5,
      temperature = 0.7,
      systemPrompt = 'あなたは親切なAIアシスタントです。'
    } = options;

    // クエリの埋め込み
    const queryEmbedding = await this.createEmbedding(query);

    // 類似文書の検索
    const searchResult = await this.vectorDB.search(this.collectionName, {
      vector: queryEmbedding,
      limit: topK,
      with_payload: true
    });

    // コンテキストの構築
    const context = searchResult
      .map((result, index) => 
        `[文書${index + 1}]\n${result.payload.text}`
      )
      .join('\n\n');

    // プロンプトの構築
    const messages = [
      { role: 'system', content: systemPrompt },
      { 
        role: 'user', 
        content: `以下の文書を参考に質問に答えてください。

参考文書:
${context}

質問:${query}

回答:`
      }
    ];

    // LLMによる回答生成
    const completion = await this.openai.chat.completions.create({
      model: 'gpt-4',
      messages,
      temperature,
      max_tokens: 1000
    });

    return {
      answer: completion.choices[0].message.content,
      sources: searchResult.map(r => ({
        text: r.payload.text,
        score: r.score,
        metadata: r.payload.metadata
      })),
      usage: completion.usage
    };
  }

  // 高度なRAG手法:HyDE (Hypothetical Document Embeddings)
  async generateWithHyDE(query: string): Promise<RAGResponse> {
    // ステップ1: 仮想回答の生成
    const hypotheticalAnswer = await this.openai.chat.completions.create({
      model: 'gpt-3.5-turbo',
      messages: [
        { 
          role: 'user', 
          content: `次の質問に対する詳細な回答を書いてください: ${query}`
        }
      ],
      temperature: 0.7
    });

    // ステップ2: 仮想回答を使って検索
    const hydeEmbedding = await this.createEmbedding(
      hypotheticalAnswer.choices[0].message.content
    );

    const searchResult = await this.vectorDB.search(this.collectionName, {
      vector: hydeEmbedding,
      limit: 5,
      with_payload: true
    });

    // ステップ3: 実際の回答生成
    return this.generate(query, { topK: 5 });
  }

  private async createEmbedding(text: string): Promise<number[]> {
    const response = await this.openai.embeddings.create({
      model: 'text-embedding-ada-002',
      input: text
    });
    return response.data[0].embedding;
  }
}

RAGの最適化テクニック

// シンプルなRAG
async function basicRAG(query: string) {
  // 1. クエリを埋め込み
  const embedding = await embed(query);
  
  // 2. トップK検索
  const docs = await vectorDB.search(embedding, 5);
  
  // 3. LLMに投げる
  return await llm.generate(query + docs);
}
// 最適化されたRAG
async function optimizedRAG(query: string) {
  // 1. クエリ拡張
  const expandedQueries = await expandQuery(query);
  
  // 2. マルチクエリ検索
  const allDocs = await Promise.all(
    expandedQueries.map(q => vectorDB.search(q, 3))
  );
  
  // 3. Re-ranking
  const rerankedDocs = await rerank(allDocs, query);
  
  // 4. コンテキスト圧縮
  const compressedContext = await compressContext(
    rerankedDocs, 
    query
  );
  
  // 5. 構造化プロンプト
  return await llm.generate({
    systemPrompt: OPTIMIZED_PROMPT,
    context: compressedContext,
    query: query,
    examples: FEW_SHOT_EXAMPLES
  });
}
基本的なRAG
// シンプルなRAG
async function basicRAG(query: string) {
  // 1. クエリを埋め込み
  const embedding = await embed(query);
  
  // 2. トップK検索
  const docs = await vectorDB.search(embedding, 5);
  
  // 3. LLMに投げる
  return await llm.generate(query + docs);
}
最適化されたRAG
// 最適化されたRAG
async function optimizedRAG(query: string) {
  // 1. クエリ拡張
  const expandedQueries = await expandQuery(query);
  
  // 2. マルチクエリ検索
  const allDocs = await Promise.all(
    expandedQueries.map(q => vectorDB.search(q, 3))
  );
  
  // 3. Re-ranking
  const rerankedDocs = await rerank(allDocs, query);
  
  // 4. コンテキスト圧縮
  const compressedContext = await compressContext(
    rerankedDocs, 
    query
  );
  
  // 5. 構造化プロンプト
  return await llm.generate({
    systemPrompt: OPTIMIZED_PROMPT,
    context: compressedContext,
    query: query,
    examples: FEW_SHOT_EXAMPLES
  });
}

セマンティック検索の構築

ハイブリッド検索の実装

// キーワード検索とベクトル検索の組み合わせ
class HybridSearch {
  private vectorDB: VectorDatabase;
  private textIndex: TextSearchIndex;

  async search(
    query: string, 
    options: HybridSearchOptions
  ): Promise<SearchResult[]> {
    const { alpha = 0.7, limit = 10 } = options;

    // 並列で両方の検索を実行
    const [vectorResults, keywordResults] = await Promise.all([
      this.vectorSearch(query, limit * 2),
      this.keywordSearch(query, limit * 2)
    ]);

    // スコアの正規化
    const normalizedVector = this.normalizeScores(vectorResults);
    const normalizedKeyword = this.normalizeScores(keywordResults);

    // ハイブリッドスコアの計算
    const hybridResults = new Map<string, number>();

    for (const result of normalizedVector) {
      const vectorScore = result.score * alpha;
      hybridResults.set(result.id, vectorScore);
    }

    for (const result of normalizedKeyword) {
      const keywordScore = result.score * (1 - alpha);
      const currentScore = hybridResults.get(result.id) || 0;
      hybridResults.set(result.id, currentScore + keywordScore);
    }

    // 結果のソートと返却
    return Array.from(hybridResults.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, limit)
      .map(([id, score]) => ({
        id,
        score,
        document: this.getDocument(id)
      }));
  }

  // Reciprocal Rank Fusion (RRF)
  async searchWithRRF(
    query: string,
    methods: SearchMethod[]
  ): Promise<SearchResult[]> {
    const k = 60; // RRFパラメータ
    const allResults = await Promise.all(
      methods.map(method => method.search(query))
    );

    const rrfScores = new Map<string, number>();

    for (const results of allResults) {
      results.forEach((result, rank) => {
        const score = 1 / (k + rank + 1);
        const currentScore = rrfScores.get(result.id) || 0;
        rrfScores.set(result.id, currentScore + score);
      });
    }

    return Array.from(rrfScores.entries())
      .sort((a, b) => b[1] - a[1])
      .map(([id, score]) => ({
        id,
        score,
        document: this.getDocument(id)
      }));
  }
}

マルチモーダル検索

// テキスト + 画像の統合検索
class MultiModalSearch {
  private textEncoder: TextEncoder;
  private imageEncoder: ImageEncoder;
  private vectorDB: VectorDatabase;

  async indexMultiModal(items: MultiModalItem[]): Promise<void> {
    const embeddings = [];

    for (const item of items) {
      // テキストと画像を同じ埋め込み空間にマップ
      const textEmbedding = item.text 
        ? await this.textEncoder.encode(item.text)
        : null;
      
      const imageEmbedding = item.image
        ? await this.imageEncoder.encode(item.image)
        : null;

      // 埋め込みの結合または平均
      const combinedEmbedding = this.combineEmbeddings(
        textEmbedding,
        imageEmbedding
      );

      embeddings.push({
        id: item.id,
        vector: combinedEmbedding,
        payload: {
          text: item.text,
          imageUrl: item.imageUrl,
          metadata: item.metadata
        }
      });
    }

    await this.vectorDB.upsert(embeddings);
  }

  // CLIP-like検索
  async searchByText(query: string): Promise<SearchResult[]> {
    const queryEmbedding = await this.textEncoder.encode(query);
    return this.vectorDB.search(queryEmbedding);
  }

  async searchByImage(imagePath: string): Promise<SearchResult[]> {
    const queryEmbedding = await this.imageEncoder.encode(imagePath);
    return this.vectorDB.search(queryEmbedding);
  }

  // クロスモーダル検索
  async crossModalSearch(
    query: { text?: string; image?: string }
  ): Promise<SearchResult[]> {
    const embeddings = [];

    if (query.text) {
      embeddings.push(await this.textEncoder.encode(query.text));
    }
    if (query.image) {
      embeddings.push(await this.imageEncoder.encode(query.image));
    }

    const combinedQuery = this.combineEmbeddings(...embeddings);
    return this.vectorDB.search(combinedQuery);
  }
}

スケーリングと最適化

インデックス構造の選択

Vector Databaseのインデックス比較
インデックス 特徴 メモリ使用 精度 速度
Flat 全探索・完全な精度 O(n) 100% 遅い
HNSW グラフベース・高速 O(n*M) 95-99% 非常に速い
IVF クラスタリング O(n) 90-95% 速い
LSH ハッシュベース O(n) 80-90% 速い
PQ 量子化・省メモリ O(n/8) 85-90% 中程度

大規模データの処理戦略

// 10億ベクトルを扱うためのシャーディング戦略
class ShardedVectorDatabase {
  private shards: VectorDatabaseShard[];
  private shardCount: number;

  constructor(shardCount: number) {
    this.shardCount = shardCount;
    this.shards = Array(shardCount)
      .fill(null)
      .map((_, i) => new VectorDatabaseShard(i));
  }

  // 一貫性のあるハッシングでシャードを決定
  private getShardIndex(id: string): number {
    const hash = this.hashString(id);
    return hash % this.shardCount;
  }

  // データの分散インデックス
  async indexDistributed(
    data: VectorData[],
    batchSize: number = 1000
  ): Promise<void> {
    // シャードごとにデータを分類
    const shardedData = new Map<number, VectorData[]>();
    
    for (const item of data) {
      const shardIndex = this.getShardIndex(item.id);
      if (!shardedData.has(shardIndex)) {
        shardedData.set(shardIndex, []);
      }
      shardedData.get(shardIndex)!.push(item);
    }

    // 並列でシャードにインデックス
    await Promise.all(
      Array.from(shardedData.entries()).map(
        ([shardIndex, shardData]) =>
          this.shards[shardIndex].indexBatch(shardData, batchSize)
      )
    );
  }

  // 分散検索
  async searchDistributed(
    query: number[],
    topK: number
  ): Promise<SearchResult[]> {
    // 各シャードで並列検索
    const shardResults = await Promise.all(
      this.shards.map(shard => 
        shard.search(query, topK)
      )
    );

    // 結果をマージしてトップKを選択
    return this.mergeResults(shardResults, topK);
  }

  // 動的リバランシング
  async rebalance(): Promise<void> {
    const loadStats = await this.getLoadStatistics();
    
    if (this.needsRebalancing(loadStats)) {
      const rebalancePlan = this.createRebalancePlan(loadStats);
      await this.executeRebalancing(rebalancePlan);
    }
  }
}

メモリ最適化テクニック

メモリ削減のベストプラクティス

  1. スカラー量子化: float32 → int8 で 75%削減
  2. Product Quantization: ベクトルを部分空間に分割
  3. 次元削減: PCA や Autoencoder で次元圧縮
  4. スパースベクトル: ゼロ要素を除外して保存
  5. 階層的インデックス: 頻度に応じてメモリ/ディスク配置
// スカラー量子化の実装例
class QuantizedVectorIndex {
  private quantizationBits: number = 8;
  private scale: number[];
  private offset: number[];

  // float32 → int8 量子化
  quantizeVector(vector: number[]): Int8Array {
    const quantized = new Int8Array(vector.length);
    
    for (let i = 0; i < vector.length; i++) {
      const normalized = (vector[i] - this.offset[i]) / this.scale[i];
      const quantizedValue = Math.round(
        normalized * (Math.pow(2, this.quantizationBits - 1) - 1)
      );
      quantized[i] = Math.max(
        -128, 
        Math.min(127, quantizedValue)
      );
    }
    
    return quantized;
  }

  // int8 → float32 逆量子化
  dequantizeVector(quantized: Int8Array): number[] {
    const vector = new Array(quantized.length);
    
    for (let i = 0; i < quantized.length; i++) {
      const normalized = quantized[i] / (Math.pow(2, this.quantizationBits - 1) - 1);
      vector[i] = normalized * this.scale[i] + this.offset[i];
    }
    
    return vector;
  }

  // 非対称量子化での検索
  async searchWithAsymmetricQuantization(
    query: number[],
    topK: number
  ): Promise<SearchResult[]> {
    // クエリは量子化しない(精度向上)
    const results = [];
    
    for (const [id, quantizedVector] of this.quantizedVectors) {
      // 量子化空間で直接距離計算
      const distance = this.asymmetricDistance(
        query,
        quantizedVector
      );
      
      results.push({ id, distance });
    }
    
    return results
      .sort((a, b) => a.distance - b.distance)
      .slice(0, topK);
  }
}

本番運用のベストプラクティス

監視とメトリクス

// Vector Database監視システム
class VectorDBMonitoring {
  private metrics: MetricsCollector;

  async collectMetrics(): Promise<SystemMetrics> {
    return {
      // パフォーマンスメトリクス
      queryLatency: {
        p50: await this.metrics.getPercentile('query_latency', 50),
        p95: await this.metrics.getPercentile('query_latency', 95),
        p99: await this.metrics.getPercentile('query_latency', 99)
      },
      
      // インデックスメトリクス
      indexMetrics: {
        totalVectors: await this.getTotalVectors(),
        indexSize: await this.getIndexSizeGB(),
        fragmentationRatio: await this.getFragmentation()
      },
      
      // システムリソース
      resources: {
        memoryUsageGB: await this.getMemoryUsage(),
        cpuUsagePercent: await this.getCPUUsage(),
        diskIOPS: await this.getDiskIOPS()
      },
      
      // 品質メトリクス
      quality: {
        recallAt10: await this.measureRecall(10),
        precisionAt10: await this.measurePrecision(10)
      }
    };
  }

  // アラート設定
  setupAlerts(): void {
    this.metrics.createAlert({
      name: 'high_query_latency',
      condition: 'p99_latency > 100ms',
      action: 'notify_oncall'
    });

    this.metrics.createAlert({
      name: 'low_recall',
      condition: 'recall_at_10 < 0.9',
      action: 'investigate_index_quality'
    });

    this.metrics.createAlert({
      name: 'memory_pressure',
      condition: 'memory_usage > 80%',
      action: 'scale_up_instance'
    });
  }
}

バックアップとリカバリ

// Vector Databaseのバックアップ戦略
class VectorDBBackup {
  async performBackup(strategy: BackupStrategy): Promise<BackupResult> {
    switch (strategy) {
      case 'incremental':
        return this.incrementalBackup();
      case 'snapshot':
        return this.snapshotBackup();
      case 'continuous':
        return this.continuousReplication();
    }
  }

  // 増分バックアップ
  private async incrementalBackup(): Promise<BackupResult> {
    const lastBackupTime = await this.getLastBackupTime();
    const changes = await this.getChangesSince(lastBackupTime);
    
    // 変更されたベクトルのみバックアップ
    const backupData = {
      timestamp: new Date(),
      type: 'incremental',
      vectors: changes.vectors,
      deletions: changes.deletions
    };
    
    await this.uploadToS3(backupData);
    
    return {
      success: true,
      backedUpVectors: changes.vectors.length,
      backupSize: this.calculateSize(backupData)
    };
  }

  // Point-in-Timeリカバリ
  async restoreToPoint(targetTime: Date): Promise<void> {
    // 最も近いスナップショットを見つける
    const baseSnapshot = await this.findNearestSnapshot(targetTime);
    
    // スナップショットを復元
    await this.restoreSnapshot(baseSnapshot);
    
    // 増分バックアップを適用
    const incrementals = await this.getIncrementalsSince(
      baseSnapshot.timestamp,
      targetTime
    );
    
    for (const incremental of incrementals) {
      await this.applyIncremental(incremental);
    }
  }
}

セキュリティ対策

Vector Databaseのセキュリティ考慮事項

  1. 埋め込みからの情報漏洩: ベクトルから元テキストを推測可能
  2. アクセス制御: ユーザーごとの検索範囲制限
  3. データ暗号化: 保存時・転送時の暗号化
  4. 監査ログ: 全クエリと更新の記録
  5. レート制限: DoS 攻撃の防止

今後の展望とまとめ

2025年後半以降のトレンド

グラフRAG主流化

知識グラフとベクトル検索の融合

エッジVector DB

モバイル・IoTデバイスでの実行

量子ベクトル検索

量子コンピュータでの超高速検索

完全自律型RAG

自己学習・自己最適化するシステム

まとめ

Vector Database は、AIアプリケーションの基盤技術として急速に進化しています。適切なプラットフォーム選定、埋め込みモデルの最適化、そして本番環境での運用ノウハウが成功の鍵となります。

技術の成熟度 95 %
エンタープライズ採用率 70 %
開発者エコシステム 85 %

重要なのは、ユースケースに応じた技術選定です。小規模な PoC なら ChromaDB、エンタープライズなら Pinecone、高性能が必要なら Qdrant、柔軟性重視なら Weaviate というように、要件に応じて選択しましょう。

この記事は役に立ちましたか?

Daily Hackでは、開発者の皆様に役立つ情報を毎日発信しています。