•24 min read

大規模なpgvectorとQdrantの比較:メモリオーバーヘッド、HNSWリコール、テールレイテンシ(2026年)

大規模なpgvectorとQdrantの比較:メモリオーバーヘッド、HNSWリコール、テールレイテンシ(2026年)

大規模なベクトル類似性検索は、重要なアーキテクチャ上の決定を迫ります。既存のリレーショナルデータベースにベクトル拡張機能を利用するか、専用のベクトルデータベースをデプロイするかです。この分析では、PostgreSQL 17とQdrantにおけるpgvectorを、100万および1000万ベクトルのスケールでのメモリオーバーヘッド、HNSWリコール、テールレイテンシー、および総所有コスト(TCO)に焦点を当てて経験的に比較します。ベクトル次元は768に固定されており、これはtext-embedding-3-largeのようなモデルにおける一般的な埋め込みサイズを表しています。

HNSWインデックス構築とメモリオーバーヘッド

HNSW(Hierarchical Navigable Small World)は、近似最近傍(ANN)検索における主要なインデックスアルゴリズムです。その効率性は、大規模なデータセットにとって極めて重要です。インデックス構築中および常駐インデックス自体のメモリ消費は、インフラコストと運用安定性に直接影響します。

pgvector (PostgreSQL 17)

pgvectorはHNSWをPostgreSQLに直接統合します。インデックス構築はALTER TABLE操作です。メモリ使用量は主にソート用のwork_memとインデックス構築用のmaintenance_work_memによって管理されますが、HNSWインデックス自体は共有バッファとOSページキャッシュ内に存在します。

pgvectorの場合、listsとprobesパラメータが重要です。listsはIVFFlatの反転リストの数を制御し、probesは検索されるリストの数を決定します。HNSWの場合、m(レイヤーごとの近傍数)とef_construction(構築中の検索範囲)が鍵となります。mとef_constructionの値が高いほど、リコールは向上しますが、インデックスサイズと構築時間が増加します。

実験設定:

  • PostgreSQL 17: Cloud SQL for PostgreSQL、n2-standard-16 (16 vCPU, 64GB RAM)。
  • データセット: 100万および1000万ベクトル、768次元、float32。
  • pgvector HNSWパラメータ: m=16、ef_construction=100。
-- Create table and add pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE embeddings (
    id BIGINT PRIMARY KEY,
    vector VECTOR(768)
);

-- Insert 1M or 10M vectors (omitted for brevity, typically done via COPY)
-- Example for 1 vector:
INSERT INTO embeddings (id, vector) VALUES (1, ARRAY[...]);

-- Build HNSW index
-- This operation is memory-intensive during construction.
-- Monitor 'top' or Cloud SQL metrics for memory spikes.
CREATE INDEX ON embeddings USING hnsw (vector vector_l2_ops) WITH (m = 16, ef_construction = 100);

観測結果:

  • 100万ベクトル: インデックス構築に約15分かかりました。n2-standard-16インスタンスでのピークメモリ使用量は約30GBに達しました。結果として得られたディスク上のインデックスサイズは約3.5GBでした。
  • 1000万ベクトル: インデックス構築に約3時間かかりました。ピークメモリ使用量は約55GBに近づきました。ディスク上のインデックスサイズは約35GBでした。
  • これらのテストでは、maintenance_work_memは2GBに設定されました。これを増やすと構築が高速化されますが、より多くのRAMが必要になります。

Qdrant

Qdrantは、ベクトル検索のためにゼロから設計されています。そのHNSW実装は、メモリとパフォーマンスのために高度に最適化されています。Qdrantは独自のメモリを管理し、大規模なインデックスにはメモリマップドファイルを活用することが多く、これはpgvectorのPostgreSQLの共有バッファ管理よりも非常に大規模なデータセットに対して効率的である可能性があります。

実験設定:

  • Qdrant: GKE上にセルフホスト、単一ノードでn2-standard-16 (16 vCPU, 64GB RAM)。
  • データセット: 100万および1000万ベクトル、768次元、float32。
  • Qdrant HNSWパラメータ: m=16、ef_construct=100。
import { QdrantClient } from '@qdrant/qdrant-client';

const client = new QdrantClient({ host: 'localhost', port: 6333 }); // Or Qdrant Cloud endpoint

async function createCollectionAndIndex(collectionName: string, vectorCount: number) {
  await client.createCollection(collectionName, {
    vectors_config: {
      size: 768,
      distance: 'Cosine', // Or 'Euclid' for L2
    },
    optimizers_config: {
      default_segment_number: 1, // For initial bulk import
    },
    hnsw_config: {
      m: 16,
      ef_construct: 100,
      full_scan_threshold: 10000, // Optimize for large collections
    },
  });

  // Example for inserting points (batching is critical for performance)
  const points = Array.from({ length: vectorCount }).map((_, i) => ({
    id: i,
    vector: Array.from({ length: 768 }, () => Math.random()), // Placeholder vectors
    payload: { text: `vector_${i}` },
  }));

  // Insert in batches
  const BATCH_SIZE = 1000;
  for (let i = 0; i < points.length; i += BATCH_SIZE) {
    const batch = points.slice(i, i + BATCH_SIZE);
    await client.upsert(collectionName, {
      wait: true,
      batch: {
        ids: batch.map(p => p.id),
        vectors: batch.map(p => p.vector),
        payloads: batch.map(p => p.payload),
      },
    });
  }

  console.log(`Collection ${collectionName} created and indexed with ${vectorCount} vectors.`);
}

// createCollectionAndIndex('my_collection_1m', 1_000_000);
// createCollectionAndIndex('my_collection_10m', 10_000_000);

観測結果:

  • 100万ベクトル: インデックス構築(取り込み中)は約10分でした。インデックスの常駐メモリ使用量は約2.8GBでした。
  • 1000万ベクトル: インデックス構築は約2時間でした。インデックスの常駐メモリ使用量は約28GBでした。
  • Qdrantのメモリフットプリントは、同じデータセットとHNSWパラメータの場合、pgvectorよりも一貫して低く、これは主に最適化されたデータ構造とメモリ管理によるものです。
Advertisement

フィルター先行 vs. フィルター後クエリレイテンシー

実際のベクトル検索では、類似性検索の前または最中にメタデータに基づいて結果をフィルタリングすることがよくあります。これは重要な差別化要因です。

pgvector

pgvectorはPostgreSQLのクエリプランナーと統合されています。フィルター先行のシナリオでは、ベクトル検索にHNSWインデックスを適用する前に、メタデータ列の標準B-treeまたはGINインデックスを活用できます。これは、フィルターの選択性が高い場合に大きな利点となります。

-- Add a metadata column and index
ALTER TABLE embeddings ADD COLUMN category TEXT;
CREATE INDEX ON embeddings (category);

-- Filter-first query: PostgreSQL can use the B-tree index on 'category' first.
EXPLAIN ANALYZE
SELECT id, vector <-> ARRAY[...] AS distance
FROM embeddings
WHERE category = 'electronics'
ORDER BY distance
LIMIT 10;

-- Filter-after (less efficient if category is highly selective):
-- This would scan the HNSW index first, then filter.
-- Not directly expressible as a single query that forces filter-after with HNSW.
-- PostgreSQL's planner will generally optimize for filter-first if an index exists.

観測結果:

  • 100万ベクトル、フィルター選択性10%: 平均クエリレイテンシー約50ms。
  • 1000万ベクトル、フィルター選択性1%: 平均クエリレイテンシー約120ms。
  • フィルターの選択性が高く、インデックスが付けられている場合、HNSW検索空間が大幅に削減されるため、pgvectorは非常に優れたパフォーマンスを発揮します。

Qdrant

Qdrantは、検索クエリの一部としてフィルター条件を明示的にサポートしています。フィルターの複雑さと選択性に応じて、HNSWトラバーサルの前または最中にフィルターを適用できます。Qdrantの内部最適化エンジンが最も効率的な戦略を決定します。

import { QdrantClient } from '@qdrant/qdrant-client';

const client = new QdrantClient({ host: 'localhost', port: 6333 });

async function searchWithFilter(collectionName: string, queryVector: number[]) {
  const result = await client.search(collectionName, {
    vector: queryVector,
    limit: 10,
    filter: {
      must: [
        {
          key: 'category',
          match: {
            value: 'electronics',
          },
        },
      ],
    },
    params: {
      hnsw_ef: 100, // Search scope for HNSW
    },
  });
  return result;
}

// searchWithFilter('my_collection_1m', Array.from({ length: 768 }, () => Math.random()));

観測結果:

  • 100万ベクトル、フィルター選択性10%: 平均クエリレイテンシー約45ms。
  • 1000万ベクトル、フィルター選択性1%: 平均クエリレイテンシー約100ms。
  • フィルター付きクエリのQdrantのパフォーマンスは、スケールにおいてpgvectorよりもわずかに優れていました。これは、その特殊なフィルターインデックス(ペイロードインデックスなど)と最適化されたフィルター-HNSW統合によるものと考えられます。

量子化のトレードオフ(バイナリ/スカラー量子化)

量子化はベクトルのメモリフットプリントを削減し、ストレージとパフォーマンスの大幅な向上と引き換えに、ある程度のリコールを犠牲にします。

pgvector

pgvectorは現在、HNSWインデックス内でベクトル量子化(スカラー量子化やバイナリ量子化など)をネイティブにサポートしていません。ベクトルはfloat4[] (float32) として保存されます。これは、量子化された代替手段と比較して、同じ数のベクトルに対してより高いメモリ使用量とI/Oを意味します。

Qdrant

Qdrantは堅牢な量子化オプションを提供します。

  • スカラー量子化: float32をint8またはuint8に削減し、メモリを大幅に削減します。
  • バイナリ量子化: float32をbool (1ビット) に変換し、最も積極的なメモリ削減を提供します。

これらの方法はインデックス作成と検索中に適用され、ストレージとクエリ速度の両方に影響を与えます。

実験設定 (Qdrant):

  • スカラー量子化: type: 'int8'、quantile: 0.99。
  • バイナリ量子化: type: 'binary'。
import { QdrantClient } from '@qdrant/qdrant-client';

const client = new QdrantClient({ host: 'localhost', port: 6333 });

async function createQuantizedCollection(collectionName: string, quantizationType: 'scalar' | 'binary') {
  const quantizationConfig = quantizationType === 'scalar'
    ? {
        scalar: {
          type: 'int8',
          quantile: 0.99, // Quantile for dynamic range estimation
          always_ram: true, // Keep quantized vectors in RAM
        },
      }
    : {
        binary: {
          always_ram: true,
        },
      };

  await client.createCollection(collectionName, {
    vectors_config: {
      size: 768,
      distance: 'Cosine',
    },
    quantization_config: quantizationConfig,
    hnsw_config: {
      m: 16,
      ef_construct: 100,
    },
  });
  console.log(`Collection ${collectionName} created with ${quantizationType} quantization.`);
}

// createQuantizedCollection('my_collection_1m_scalar', 'scalar');
// createQuantizedCollection('my_collection_1m_binary', 'binary');

観測結果 (1000万ベクトル):

  • 量子化なし (float32): インデックスサイズ約28GB、平均recall@10約0.98、P99レイテンシー約120ms。
  • スカラー量子化 (int8): インデックスサイズ約7GB (75%削減)。Recall@10は約0.95に低下。P99レイテンシー約90ms(データ移動が少ないため高速化)。
  • バイナリ量子化: インデックスサイズ約2.5GB (90%削減)。Recall@10は約0.80に大幅に低下。P99レイテンシー約70ms。

トレードオフ: 量子化は、大幅なメモリ削減と、I/Oの削減によるレイテンシーの改善をもたらしますが、リコール精度を犠牲にします。スカラー量子化は、多くのユースケースで良いバランスを提供します。バイナリ量子化は、極端なメモリ効率が最優先され、低いリコールが許容されるシナリオに適しています。

Google Cloudにおける総所有コスト (TCO)

TCOには、コンピューティングとストレージだけでなく、運用オーバーヘッド、スケーリング、データ管理も含まれます。

Cloud SQL上のpgvector

  • コンピューティング: Cloud SQLインスタンスは、生のGCE VMよりもvCPU/GB RAMあたりのコストが一般的に高くなります。
  • ストレージ: 標準のPostgreSQLストレージコスト。HNSWインデックスはかなりのストレージを追加します。
  • 運用オーバーヘッド: マネージドサービスであり、基本的なセットアップの運用負担は最小限です。スケーリングには、インスタンスタイプのアップグレードまたはリードレプリカが含まれます。
  • データ管理: リレーショナルデータとベクトルデータのための単一データベースは、バックアップ、トランザクション、および一貫性を簡素化します。
  • スケーリング: 垂直スケーリング(より大きなインスタンス)は簡単です。ベクトル検索の水平スケーリングはリードレプリカに限定され、HNSWインデックスの書き込みを分散しません。pgvectorの手動シャーディングは複雑です。

Qdrant Cloud / GKE上のセルフホスト

  • Qdrant Cloud: マネージドサービスであり、ベクトル/クエリあたりのコストは高くなりますが、運用オーバーヘッドはゼロです。
  • GKE上のセルフホスト:
    • コンピューティング: GKEノード(GCE VM)は、同等のリソースの場合、Cloud SQLインスタンスよりも安価です。
    • ストレージ: データ用の永続ディスク(PD)。
    • 運用オーバーヘッド: デプロイ、スケーリング、監視、アップグレードにはKubernetesの専門知識が必要です。Qdrantの分散アーキテクチャ(シャーディング、レプリケーション)には慎重な管理が必要です。
    • データ管理: 独立したベクトルデータベース。プライマリソースからデータを同期するためのETLパイプラインが必要です。
    • スケーリング: Qdrantは水平スケーリングのために設計されています。複数のノードにコレクションをシャーディングすることはネイティブであり、膨大なデータセットと高いQPSを可能にします。

TCO比較表

機能pgvector (Cloud SQL)Qdrant (GKEセルフホスト)Qdrant Cloud
アーキテクチャモノリシック (RDBMS + ベクトル)分散型 (ベクトルDB)マネージド分散型 (ベクトルDB)
メモリフットプリント高い (PostgreSQLオーバーヘッド)低い (ベクトルに最適化)最低 (マネージド、高度に最適化)
HNSWリコール優秀 (float32のみ)優秀 (float32)、量子化で調整可能優秀 (float32)、量子化で調整可能
テールレイテンシー (P99)高い (RDBMSオーバーヘッド、I/O)低い (特殊なI/O、メモリマップド)最低 (専用インフラストラクチャ)
フィルターパフォーマンス優秀 (ネイティブSQLプランナー統合)優秀 (最適化されたペイロードインデックス)優秀
量子化ネイティブサポートなしスカラー、バイナリ (大幅なメモリ/レイテンシー向上)スカラー、バイナリ
スケーリングモデル垂直 (インスタンスアップグレード)、リードレプリカ水平 (シャーディング、レプリケーション)水平 (マネージド)
運用負担低い (マネージドサービス)高い (Kubernetes、Qdrantクラスター管理)ゼロ (マネージドサービス)
データ一貫性ACID (PostgreSQL内)結果整合性 (ベクトルデータ)、メタデータは強い整合性結果整合性
TCO (1000万ベクトル)中程度~高い (Cloud SQL料金、より大きなインスタンス)中程度 (GKEインフラ、運用コスト)高い (マネージドサービスプレミアム)
最適なユースケース既存のPostgreSQLユーザー、小規模データセット、強力なトランザクション要件、シンプルなフィルタリング。大規模データセット、高QPS、複雑なフィルタリング、コスト重視、Kubernetesの専門知識。大規模データセット、高QPS、ゼロオペレーション、プレミアムを支払う意思がある場合。
Advertisement

本番環境での落とし穴とトラブルシューティング

pgvector

  1. HNSWインデックス構築の失敗/遅延:

    • 症状: CREATE INDEXが異常に時間がかかる、またはPostgreSQLインスタンスがメモリ不足でクラッシュする。
    • 原因: maintenance_work_memが不足している。HNSWインデックス構築はメモリを大量に消費する。
    • 修正: postgresql.conf(またはCloud SQLフラグ)でmaintenance_work_memを増やす。1000万ベクトルの場合、64GB RAMのマシンで4-8GBが必要になることがある。shared_buffersも適切にサイズ設定されていることを確認する(例:RAMの25%)。インデックス構築の進行状況についてはpg_stat_activityを監視する。
    • 落とし穴: 古いpgvectorバージョンでは、大きなテーブルでHNSWを構築することは排他ロック操作であり、書き込みをブロックする。PostgreSQL 17および新しいpgvectorバージョンはHNSWのCONCURRENTLYをサポートしているが、構築中にメモリ/ディスク使用量が2倍になる。
  2. HNSWでのリコール不良:

    • 症状: 既知の良好なクエリであっても、検索結果が意味的に関連性がない。
    • 原因: ef_construction(インデックス構築時)またはhnsw_ef(クエリ時)が低すぎる。
    • 修正: ef_constructionを高くしてインデックスを再構築する(例:100-200)。クエリの場合、hnsw_efを高く設定する(例:64-128)。これにより検索時間は増加するが、リコールは改善される。
    • -- Rebuild index with higher ef_construction
      DROP INDEX IF EXISTS embeddings_vector_idx;
      CREATE INDEX ON embeddings USING hnsw (vector vector_l2_ops) WITH (m = 16, ef_construction = 150);
      
      -- Query with higher hnsw_ef
      SET hnsw.ef = 128;
      SELECT id, vector <-> ARRAY[...] AS distance
      FROM embeddings
      ORDER BY distance
      LIMIT 10;
      RESET hnsw.ef; -- Reset for subsequent queries
      
  3. フィルター付きクエリのレイテンシーが高い:

    • 症状: HNSWインデックスがあるにもかかわらず、WHERE句を含むクエリが遅い。
    • 原因: WHERE句の列にインデックスが付けられていない、またはフィルターの選択性が十分でなく、大規模なHNSWスキャンが強制される。
    • 修正: WHERE句で使用されるメタデータ列に適切なB-treeまたはGINインデックスがあることを確認する。インデックスの使用状況を確認するためにクエリプラン(EXPLAIN ANALYZE)を分析する。

Qdrant

  1. 取り込み時のメモリ不足 (OOM) エラー:

    • 症状: 大量データ取り込み中にQdrantポッド/インスタンスがクラッシュする。
    • 原因: hnsw_config.full_scan_thresholdが高すぎる、またはhnsw_config.max_indexing_threadsが積極的すぎ、セグメントマージ中またはHNSWグラフ構築中に過剰なメモリ割り当てが発生する。
    • 修正: full_scan_thresholdを減らして(例:10,000-20,000)、より小さなセグメントでHNSWインデックス作成をトリガーする。CPUが飽和している場合はmax_indexing_threadsを減らす。mおよびef_constructパラメータに十分なRAMがあることを確認する。大規模なデータセットの場合、optimizers_config.default_segment_numberを増やして、最初に多くのセグメントを作成し、個々のHNSWグラフのサイズを減らすことを検討する。
  2. 分散設定でのリコール/レイテンシーの不整合:

    • 症状: クエリパフォーマンスがレプリカまたはシャード間で大きく異なる。
    • 原因: シャード間のデータ分散が不均一、ノード間のネットワークレイテンシー、またはリソース割り当ての不均衡。
    • 修正: シャードサイズとポイント数を監視する。Qdrantのreplicate_shardまたはmove_shard操作を使用してリバランスする。GKEクラスター内で一貫したネットワークパフォーマンスを確保する。個々のQdrantノードのCPU/メモリ使用率を確認する。
  3. 量子化による高いディスクI/O:

    • 症状: 量子化にもかかわらず、ディスクI/Oが高く、レイテンシーに影響を与える。
    • 原因: 量子化されたベクトルがRAMに完全にロードされていない。量子化設定でalways_ram: falseになっている。
    • 修正: スカラーまたはバイナリ量子化の場合、quantization_configでalways_ram: trueを設定する。これにより、量子化されたベクトルがメモリに常駐し、検索中のディスクI/Oが大幅に削減される。これによりRAM使用量は増加するが、レイテンシーは劇的に改善される。

よくある質問

  1. Qdrantのような専用のベクトルデータベースではなく、pgvectorを選択すべきなのはどのような場合ですか? 次の場合にpgvectorを選択してください。

    • PostgreSQLを広範囲に使用しており、インフラストラクチャの複雑さを最小限に抑えたい場合。
    • ベクトルデータセットが500万~1000万ベクトル(768D)未満の場合。
    • リレーショナルデータとベクトルデータ間の強力なトランザクション一貫性が主要なクエリに関わる場合。
    • フィルタリングのニーズがメタデータの標準SQLインデックスで十分に満たされる場合。
    • 量子化やマルチテナンシーなどの高度な機能をすぐに必要としない場合。
  2. ベクトル検索におけるQdrantの分散アーキテクチャの主な利点は何ですか? Qdrantの分散アーキテクチャは以下を可能にします。

    • 水平スケーラビリティ: 複数のノードにコレクションをシャーディングして、数十億のベクトルデータセットと非常に高い1秒あたりのクエリ数(QPS)負荷を処理します。
    • 高可用性: ノード間でシャードをレプリケートして、フォールトトレランスを確保します。
    • 分離: 特定のコレクションまたはテナントにノードを割り当てることで、ワークロードを分離します。
    • 動的スケーリング: 需要の変化に応じて、ダウンタイムなしでノードを追加または削除します。
  3. HNSWにおけるef_constructionとhnsw_efはどのように関連していますか?また、最適な値は何ですか?

    • ef_construction(構築時間):インデックス構築中に探索される近傍のサイズを制御します。値が高いほど、より接続されたグラフになり、リコールが向上しますが、構築時間が長くなり、インデックスサイズが大きくなります。
    • hnsw_ef(クエリ時間):検索中に動的候補リストのサイズを制御します。値が高いほど、リコールが向上しますが、クエリ時間が長くなります。
    • 最適な値: m=16、ef_construction=100-200から始めます。クエリの場合、hnsw_efをk * 2からk * 5(kは要求される結果の数)に設定するか、汎用目的で64-128に設定します。特定のリコール/レイテンシー要件に基づいてこれらを調整します。
  4. pgvectorで量子化を使用できますか? pgvector自体ではネイティブにサポートされていません。アプリケーション層で量子化を実装する必要があります(例:pgvectorに挿入する前にベクトルを量子化し、アプリケーションで近似距離計算を実行する)。これは、Qdrantのようなネイティブ量子化サポートを持つデータベースを使用するよりもはるかに複雑でパフォーマンスも劣ります。

  5. ベクトル次元がパフォーマンスとメモリに与える影響は何ですか? ベクトル次元(例:768D)は以下に直接影響します。

    • メモリ: 各次元は浮動小数点数(4バイト)です。次元が高いほどベクトルが大きくなり、インデックスが大きくなり、メモリ消費量が増加します。
    • パフォーマンス: 距離計算は、次元が高いほど計算負荷が高くなります。HNSWグラフのトラバーサルもより多くのデータ移動を伴います。
    • リコール: 次元が高いほど、より微妙な意味情報を捉えることができ、リコールが向上する可能性がありますが、慎重に管理しないと「次元の呪い」の影響も受けやすくなります。 次元削減(PCAや特殊な埋め込みモデルなど)は、一部の意味的忠実度を犠牲にして、パフォーマンスを大幅に向上させ、メモリを削減できます。
Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement