CLOVER🍀

That was when it all began.

Ubuntu Linux 22.04 LTSにベクトルデータベースQdrantをインストールして試す

これは、なにをしたくて書いたもの?

前に少し、ベクトルデータベースにはどのようなものがあるのか調べてみました。

ベクトルデータベースってどういうものがある? - CLOVER🍀

そろそろ、この中からなにか選んで、ベクトルデータベースというものを試してみようかなと思いまして。

Qdrantを試してみることにしました。

Qdrant

QdrantのWebサイトはこちら。

Qdrant - Vector Database

GitHubリポジトリーはこちら。

GitHub - qdrant/qdrant: Qdrant - High-performance, massive-scale Vector Database for the next generation of AI. Also available in the cloud https://cloud.qdrant.io/

Webサイトのトップページによると、特徴は以下のようです。

  • OpenAPI 3仕様に沿ったドキュメントを提供し、多くのプログラミング言語に対するクライアントを作成可能
  • ANN Search(Approximate Nearest Neighbor Search)のためのHNSWアルゴリズムを独自にカスタム実装
  • ベクトルに関連付けられたペイロードの追加をサポートし、ペイロードを保存するだけではなくペイロードの値に基づいて結果をフィルタリングすることも可能
  • 文字列一致、数値範囲、地理的位置などを含む、様々なデータ型とクエリーをサポート
  • 分散型で、水平スケーリングが可能
  • Rustで実装されており、リソースを有効活用できる

Qdrantを使った検索をどのような分野で使うとよさそうなのかは、こちらのページを見るのがよいでしょう。

Vector Search Solutions - Qdrant

類似画像検索、セマンティックテキスト検索、レコメンデーション、チャットボット、マッチングエンジン、異常検知などが
挙げられています。

ドキュメントはこちらです。

Qdrant Documentation - Qdrant

最初に読むのはこちらのページでしょうか。

What is Qdrant? - Qdrant

ざっくり見てみましょう。

  • Qdrantは、ペイロードの追加と保存、検索、管理に便利なAPIを提供するプロダクションレディなベクトル類似性検索エンジン
  • ベクトルデータベースは、高次元ベクトルを効率的に保存、クエリーできるように設計されたデータベースの一種
    • ベクトルデータベースでは、ベクトルにIDとペイロードを加えたものを収集する
    • ここでいう「ベクトル」とはオブジェクトまたはデータポイントの数学的表現を表す
  • ベクトルデータベースでは、距離メトリクスに基づいてのクエリーベクトルに最も近いベクトルを見つけることで、高速な類似性検索とセマンティック検索が行えるようになる
    • 一般的に使用される距離メトリクスは、ユークリッド距離、コサイン類似度、ドット積の3つであり、Qdrantではすべてサポートしている
      • コサイン類似度 … 2つのものがどれだけ似ているか測定する方法(距離ではない)で、2つの文書または文が互いにどれだけ似ているかを比較するためによく利用される
      • ドット積 … 2つのものがどれだ似ているか測定する方法で、機械学習やデータサイエンスで数値を扱う時によく利用される。2セットの数値がどれだけ一致しているかを示すもの
      • ユークリッド距離 … 地図上の2つの場所の距離を測定する方法で、2つのデータポイントがどの程度類似しているか、もしくは異なっているかを測定することに、主に機械学習で利用される
  • Qdrantの主な概念
    • コレクション … 検索できる名前付きのポイント(ペイロードを含むベクトル)の集合
    • 距離メトリクス … ベクトル間の類似性を測るのに使用され、コレクションの作成時に指定する
    • ポイント … Qdrantが操作する中心的なエンティティであり、ベクトルとオプションのidおよびペイロードで構成される
      • Points - Qdrant
      • id … ベクトルの一意の識別子
      • ベクトル … 画像、音、ドキュメント、ビデオなどのデータの高次元表現
      • ペイロード … ベクトルに追加できるJSONオブジェクト
    • ストレージ … Qdrantのデータの保存先で、メモリとディスクから選択
    • クライアント … Qdrantへの接続に使用できるプログラミング言語

RDBMSでいうテーブルがコレクションで、レコードがポイント、という感じでしょうか。クエリーの戦略はコレクションの作成時に
決まるものみたいですね。

ベクトル検索の基本は、こちら。

Vector Search Basics - Qdrant

ここは、次回また見たいと思います。

Qdrantのインストール方法は、Docker、Kubernetes、Qdrant Cloud、ソースコードのビルドが紹介されています。

Installation - Qdrant

また、記載はありませんが、ビルド済みのバイナリをダウンロードしても良さそうです。

Releases · qdrant/qdrant · GitHub

今回は、とりあえずビルド済みのバイナリをダウンロードして動かしてみたいと思います。

なぜQdrantか?

Qdrantを使う前に、ベクトルデータベースを使ってみるにあたり、どうしてQdrantを選んだのかを書いておきましょう。

ベクトルデータベースに興味を持ったのはLLMの文脈でよく登場するからと、最終的にはRAGなども試してみたいからです。
この時、ハイブリッド検索などもやってみたいので、最終的にはElasticsearchのような全文検索とベクトル検索の両方ができる
データストアを選ぶかもしれません。

という一方で、ベクトルデータベースに特化しているQdrantを選んだのは以下の理由です。

  • ベクトルデータベースという知らない概念を学ぶのであれば、その分野に特化したプロダクトのドキュメントなどの方が情報が多く、深い
  • 手元で動かしたいので、SaaSではなくセルフホスティング可能なものがよい
  • 最終的にはPython以外からも使いたいので、組み込みモードのみをサポートしているプロダクトは避けたい(クライアント/サーバー形態が利用できて欲しい)

ハイブリッドなデータストアよりもベクトルデータベース専用のものを選んでいるのは、ドキュメントなどを見ることによる
ベクトルデータベースそのものに対する理解を期待しています。

このあたりの条件でベクトルデータベースを眺めていき、よさそうだったのがQdrantだったということです。

現時点ではまずはベクトルデータベースを1種学んでみたらいいかなと思っているので、他のベクトルデータベースに特化した
データストアを扱う予定はありませんが、実際どうなるかはQdrantを触ってみてどれくらい興味を持つかですね。

Qdrantは分散構成にできそうなところにも興味がありますが、そちらまで手を出すかはまだわかりません。

環境

今回の環境はこちら。Ubuntu Linux 22.04 LTSです。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.3 LTS
Release:        22.04
Codename:       jammy


$ uname -srvmpio
Linux 5.15.0-92-generic #102-Ubuntu SMP Wed Jan 10 09:33:48 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

Qdrantにアクセスする際にしようするPython。

$ python3 --version
Python 3.10.12


$ pip3 --version
pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)

Qdrantをインストールする

それでは、Qdrantをインストールします。

こちらから最新のリリースをダウンロードします。

Releases · qdrant/qdrant · GitHub

$ curl -OL https://github.com/qdrant/qdrant/releases/download/v1.7.3/qdrant-x86_64-unknown-linux-gnu.tar.gz

展開。

$ tar xvf qdrant-x86_64-unknown-linux-gnu.tar.gz
qdrant

シングルバイナリのようです。

バージョン。

$ ./qdrant --version
qdrant 1.7.3

起動。

$ ./qdrant
           _                 _
  __ _  __| |_ __ __ _ _ __ | |_
 / _` |/ _` | '__/ _` | '_ \| __|
| (_| | (_| | | | (_| | | | | |_
 \__, |\__,_|_|  \__,_|_| |_|\__|
    |_|

Access web UI at http://0.0.0.0:6333/dashboard

2024-01-25T14:41:08.535870Z  WARN qdrant::settings: Config file not found: config/config
2024-01-25T14:41:08.535964Z  WARN qdrant::settings: Config file not found: config/development
2024-01-25T14:41:08.536773Z  INFO storage::content_manager::consensus::persistent: Loading raft state from ./storage/raft_state.json
2024-01-25T14:41:08.614482Z  INFO qdrant: Distributed mode disabled
2024-01-25T14:41:08.614537Z  INFO qdrant: Telemetry reporting enabled, id: 515698cc-9357-487f-b0d2-c5732767837f
2024-01-25T14:41:08.619827Z  INFO qdrant::tonic: Qdrant gRPC listening on 6334
2024-01-25T14:41:08.619867Z  INFO qdrant::tonic: TLS disabled for gRPC API
2024-01-25T14:41:08.620446Z  WARN qdrant::actix: Static content folder for Web UI './static' does not exist
2024-01-25T14:41:08.621420Z  INFO qdrant::actix: TLS disabled for REST API
2024-01-25T14:41:08.621484Z  INFO qdrant::actix: Qdrant HTTP listening on 6333
2024-01-25T14:41:08.621500Z  INFO actix_server::builder: Starting 1 workers
2024-01-25T14:41:08.621517Z  INFO actix_server::server: Actix runtime found; starting in Actix runtime

起動がとても速いです。

Qdrantが使用するポートはこちらに記載があります。

Quickstart - Qdrant

http://localhost:6333(REST API)、http://localhost:6333/dashboard(Web UI)、http://localhost:6334(GRPC API)の3つのようです。

なお、このバイナリにはWeb UIは含まれていないようです。Dockerイメージには含まれていました。

Dockerfileを見てみると、別リポジトリーにあるようですね。

qdrant/Dockerfile at v1.7.3 · qdrant/qdrant · GitHub

https://github.com/qdrant/qdrant/blob/v1.7.3/tools/sync-web-ui.sh

こちらです。

GitHub - qdrant/qdrant-web-ui: Self-hosted web UI for Qdrant

追加しましょう。

ダウンロードして展開。

$ curl -LO https://github.com/qdrant/qdrant-web-ui/releases/download/v0.1.20/dist-qdrant.zip
$ unzip dist-qdrant.zip

展開後はdistディレクトリが現れます。こういうディレクトリ構造になっていて

$ tree dist -d
dist
└── assets

1 directory

assets配下にはJavaScriptやCSSが入っています。

dist直下はこんな感じですね。

$ ll dist
total 1100
drwxr-xr-x 3 xxxxx xxxxx   4096 Dec  7 16:31 ./
drwxr-x--- 7 xxxxx xxxxx   4096 Jan 28 08:50 ../
drwxr-xr-x 2 xxxxx xxxxx   4096 Dec  7 16:31 assets/
-rw-r--r-- 1 xxxxx xxxxx  15086 Dec  7 16:31 favicon.ico
-rw-r--r-- 1 xxxxx xxxxx   1781 Dec  7 16:31 index.html
-rw-r--r-- 1 xxxxx xxxxx   8870 Dec  7 16:31 logo192.png
-rw-r--r-- 1 xxxxx xxxxx  28384 Dec  7 16:31 logo512.png
-rw-r--r-- 1 xxxxx xxxxx   9339 Dec  7 16:31 logo.png
-rw-r--r-- 1 xxxxx xxxxx    484 Dec  7 16:31 manifest.json
-rw-r--r-- 1 xxxxx xxxxx 239641 Dec  7 16:31 openapi.json
-rw-r--r-- 1 xxxxx xxxxx 789035 Dec  7 16:31 qdrant-web-ui.spdx.json
-rw-r--r-- 1 xxxxx xxxxx     67 Dec  7 16:31 robots.txt

Qdrantのスクリプトを見ると、このdistディレクトリの中身をstaticというディレクトリに入れればいいみたいです。

$ mkdir static
$ mv dist/* static/

Qdrantを起動。

$ ./qdrant

http://[Qdrantが起動しているホスト]:6333/dashboardにアクセスすると、Web UIが現れます。

これで、Qdrantサーバーは起動できました。

QdrantのQuickstartを試す

では、Qdrantを使ってみましょう。

今回はQuickstartに沿って使ってみたいと思います。

Quickstart - Qdrant

Quickstartで使われているのはPython、TypeScript、Rust、Javaですが、今回はPythonを使います。

インストール方法は書かれていないので、PythonのQdrantクライアントのGitHubリポジトリを確認

GitHub - qdrant/qdrant-client: Python client for Qdrant vector search engine

QdrantのOpenAPI定義に従ってREST APIを呼び出すクライアントのようです。

ReDoc

インストール。

$ pip3 install qdrant-client

バージョン。

$ pip3 list
Package           Version
----------------- ----------
annotated-types   0.6.0
anyio             4.2.0
certifi           2023.11.17
exceptiongroup    1.2.0
grpcio            1.60.0
grpcio-tools      1.60.0
h11               0.14.0
h2                4.1.0
hpack             4.0.0
httpcore          1.0.2
httpx             0.26.0
hyperframe        6.0.1
idna              3.6
numpy             1.26.3
pip               22.0.2
portalocker       2.8.2
protobuf          4.25.2
pydantic          2.5.3
pydantic_core     2.14.6
qdrant-client     1.7.1
setuptools        59.6.0
sniffio           1.3.0
typing_extensions 4.9.0
urllib3           2.1.0

Python Qdrant Client(qdrant-client)は1.7.1です。

ちなみに、qdrant-clientにはQdrantサーバーなしで動作するローカルモードという機能もあるみたいです。

Python Qdrant Client / Local mode

QdrantクライアントからQdrantサーバーへの接続は、以下の記述で行うようです。

from qdrant_client import QdrantClient

client = QdrantClient(host="localhost", port=6333)

まずはコレクションを作成してみます。コレクションはベクトルデータ(ポイント)を保存するものでした。

``create_collection.py

from qdrant_client import QdrantClient
from qdrant_client.http.models import Distance, VectorParams

client = QdrantClient(host="localhost", port=6333)

client.create_collection(
    collection_name="test_collection",
    vectors_config=VectorParams(size=4, distance=Distance.DOT)
)

コレクション名、ベクトルのサイズ(次元数)と距離メトリクスを指定しています。距離メトリクスはドット積ですね。

collections / Create collection

実行。

$ python3 create_collection.py

Web UIを見ると、コレクションが作成できたことが確認できます。

ちなみに、同じコードを2回実行するとエラーになりますね。

$ python3 create_collection.py
Traceback (most recent call last):
  File "/path/to/create_collection.py", line 6, in <module>
    client.create_collection(
  File "/path/to/venv/lib/python3.10/site-packages/qdrant_client/qdrant_client.py", line 1669, in create_collection
    return self._client.create_collection(
  File "/path/to/venv/lib/python3.10/site-packages/qdrant_client/qdrant_remote.py", line 2161, in create_collection
    result: Optional[bool] = self.http.collections_api.create_collection(
  File "/path/to/venv/lib/python3.10/site-packages/qdrant_client/http/api/collections_api.py", line 1118, in create_collection
    return self._build_for_create_collection(
  File "/path/to/venv/lib/python3.10/site-packages/qdrant_client/http/api/collections_api.py", line 96, in _build_for_create_collection
    return self.api_client.request(
  File "/path/to/venv/lib/python3.10/site-packages/qdrant_client/http/api_client.py", line 74, in request
    return self.send(request, type_)
  File "/path/to/venv/lib/python3.10/site-packages/qdrant_client/http/api_client.py", line 97, in send
    raise UnexpectedResponse.for_response(response)
qdrant_client.http.exceptions.UnexpectedResponse: Unexpected Response: 400 (Bad Request)
Raw response content:
b'{"status":{"error":"Wrong input: Collection `test_collection` already exists!"},"time":0.00003701}'

ベクトルを登録します。Quickstartで使っているのは、upsertというすでにポイントが存在していれば上書きするものです。

points / Upsert points

ポイントというのは、Qdrantが操作する中心的なエンティティであり、ベクトルとオプションのidおよびペイロードで構成される、
というものでした。

ソースコードはこちら。

add_vectors.py

from qdrant_client import QdrantClient
from qdrant_client.http.models import PointStruct

client = QdrantClient(host="localhost", port=6333)

operation_info = client.upsert(
    collection_name="test_collection",
    wait=True,
    points=[
        PointStruct(id=1, vector=[0.05, 0.61, 0.76, 0.74], payload={"city": "Berlin"}),
        PointStruct(id=2, vector=[0.19, 0.81, 0.75, 0.11], payload={"city": "London"}),
        PointStruct(id=3, vector=[0.36, 0.55, 0.47, 0.94], payload={"city": "Moscow"}),
        PointStruct(id=4, vector=[0.18, 0.01, 0.85, 0.80], payload={"city": "New York"}),
        PointStruct(id=5, vector=[0.24, 0.18, 0.22, 0.44], payload={"city": "Beijing"}),
        PointStruct(id=6, vector=[0.35, 0.08, 0.11, 0.44], payload={"city": "Mumbai"}),
    ]
)

print(operation_info)

追加するベクトルは、コレクションに指定したように4次元ですね。最後に実行結果を出力しています。

waitにしているので、変更内容が実際に適用されるまで待つようになります。

実行。

$ python3 add_vectors.py
operation_id=0 status=<UpdateStatus.COMPLETED: 'completed'>

Web UIでの確認。

次は検索してみます。

points / Search points

run_query.py

from qdrant_client import QdrantClient

client = QdrantClient(host="localhost", port=6333)

search_results = client.search(
    collection_name="test_collection",
    query_vector=[0.2, 0.1, 0.9, 0.7],
    limit=3
)

for result in search_results:
    print(result)

結果はScoredPointというクラスのリストとして返ってくるようです。

実行。

$ python3 run_query.py
id=4 version=0 score=1.362 payload={'city': 'New York'} vector=None shard_key=None
id=1 version=0 score=1.273 payload={'city': 'Berlin'} vector=None shard_key=None
id=3 version=0 score=1.208 payload={'city': 'Moscow'} vector=None shard_key=None

コレクションに指定している距離メトリクスがドット積なので、検索条件に指定したベクトルに「似ている」ものの上位3件(似ているものの
降順)が取得できた、ということになっているようです。

デフォルトではペイロードおよびベクトルは結果に含まれないようです(ペイロードは返ってきていますが…)。

最後に、検索時にペイロードにフィルターを加えて結果を絞り込みます。

add_filter.py

from qdrant_client import QdrantClient
from qdrant_client.http.models import FieldCondition, Filter, MatchValue

client = QdrantClient(host="localhost", port=6333)


search_results = client.search(
    collection_name="test_collection",
    query_vector=[0.2, 0.1, 0.9, 0.7],
    query_filter=Filter(
        must=[FieldCondition(key="city", match=MatchValue(value="London"))]
    ),
    with_payload=True,
    limit=3
)

for result in search_results:
    print(result)

cityキーの値にLongonを含むものに絞り込んでいます。

実行。

$ python3 add_filter.py
id=2 version=0 score=0.871 payload={'city': 'London'} vector=None shard_key=None

実際にフィルターを使う時は、ペイロードにインデックスを作成することを推奨しているようです。

To make filtered search fast on real datasets, we highly recommend to create payload indexes!

ここまでで、Quickstartの内容は終わりです。

おわりに

まずはQuickstartをなぞっただけですが、少し雰囲気はわかった気がします。

ここから少しずつベクトルデータベースに慣れていければいいかなと思います。