Qdrantの構造を理解する

# 概要
Vector databaseの1つであるQdrantを利用するにあたって、その内部構造を理解する必要があると感じたので、調査した内容を記載します。

# Qdrantの全体像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510470/e8049b5c-b446-deff-773b-e752eeb19749.png)
[出典](https://qdrant.tech/documentation/overview/#high-level-overview-of-qdrants-architecture)

こちらの絵が非常にわかりやすいです。
詳細な説明は[公式ドキュメント](https://qdrant.tech/documentation/overview/#high-level-overview-of-qdrants-architecture)に譲りますが、重要だと感じた要素を簡単に整理します。

|  用語  |  意味  |
| ---- | ---- |
|  Collection  |  RDBにおけるテーブルに相当する概念。Pointの集合  |
|  Point  |  RDBにおけるレコードに相当する概念。Id,Vector,Payloadで構成される  |
|  Id  |  一意の識別子  |
|  Vector  |  画像、音声、ドキュメント、ビデオなどのデータの高次元表現  |
|  Payload  |  Vectorに追加されたJSON形式のデータ  |

`Payload` を用いて類似性検索時にフィルタリングできるというのが、Qdrantの特徴のようです。

また、ストレージの仕組みについては2つの方式があるようです。
|  用語  |  意味  |
| ---- | ---- |
|  In-memory  |  Vectorをメモリに格納する方式。高速  |
|  Memmap  |  Vectorをディスクに格納する方式。ディスク上のファイルを、仮想メモリのアドレス空間にマッピングする。十分なメモリがあれば、In-memoryと同じくらい高速。Qdrantマシンが高速なディスクで稼働し、大規模なCollectionを扱う場合に推奨される方式  |

次に、CollectionやPointの中身を実際に触ってみようと思います。
私はpythonに明るくないため、curlで操作します。

# 環境
下記のバージョンで確認をしました。
Qdrant 1.9.4
Docker 24.0.5
WSL2 Ubuntu 22.04 on Windows

# Qdrantインストール
インストール方法は[こちら](https://qdrant.tech/documentation/guides/installation/)を参照してください。
私は今回、Dockerでインストールしました。
```
$ docker run -d -p 6333:6333 \
    -v $(pwd)/path/to/data:/qdrant/storage \
    qdrant/qdrant
```

# Collection作成、確認
QdrantのAPIドキュメントに[サンプル](https://qdrant.github.io/qdrant/redoc/index.html#section/Examples)が載っていたので利用させていただきます。
`test_collection`という名前のCollectionを作成します。
今回は指定していませんが、`"on_disk": true`と設定することで、前述のMemmap利用モードになるようです。
詳細は[こちら](https://qdrant.tech/documentation/concepts/storage/#configuring-memmap-storage)をご確認下さい。
```
$ curl -X PUT 'http://localhost:6333/collections/test_collection' \
  -H 'Content-Type: application/json' \
  --data-raw '{
    "vectors": {
      "size": 4,
      "distance": "Dot"
    }
  }'
```

QdrantはWebUI機能も備えており、ブラウザから下記のURLでダッシュボード画面にアクセスできます。ダッシュボード画面から、Qdrantに登録したデータが閲覧可能なため、実運用の際には6333 Portを外部公開しないなど、慎重に取り扱う必要がありそうです。
`http://localhost:6333/dashboard`

ダッシュボードを開くと、`test_collection`が作成されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510470/644df680-73bb-1fad-1dfd-34530c4cd28c.png)

curlで確認することも可能です。次のコマンドではCollectionの一覧を取得しています。
```
$ curl -X GET "http://localhost:6333/collections" -H "Content-Type: application/json"
{"result":{"collections":[{"name":"test_collection"}]},"status":"ok","time":4.5e-6}
```

次のコマンドでは、作成した`test_collection`の詳細情報を取得しています。
```
$ curl -X GET "http://localhost:6333/collections/test_collection" -H "Content-Type: application/json"
{"result":{"status":"green","optimizer_status":"ok","indexed_vectors_count":0,"points_count":0,"segments_count":8,"config":{"params":{"vectors":{"size":4,"distance":"Dot"},"shard_number":1,"replication_factor":1,"write_consistency_factor":1,"on_disk_payload":true},"hnsw_config":{"m":16,"ef_construct":100,"full_scan_threshold":10000,"max_indexing_threads":0,"on_disk":false},"optimizer_config":{"deleted_threshold":0.2,"vacuum_min_vector_number":1000,"default_segment_number":0,"max_segment_size":null,"memmap_threshold":null,"indexing_threshold":20000,"flush_interval_sec":5,"max_optimization_threads":null},"wal_config":{"wal_capacity_mb":32,"wal_segments_ahead":0},"quantization_config":null},"payload_schema":{}},"status":"ok","time":0.0000653}
```

# Point作成、確認

こちらもサンプルがあったので利用させていただきます。Pointを6つPUTしてみます。
コマンドを見ると、Pointは以下の3つの要素から構成されていることが確かに分かります。
・id
・vector
・payLoad


```
$ curl -L -X PUT 'http://localhost:6333/collections/test_collection/points?wait=true' \
 -H 'Content-Type: application/json'\
 --data-raw '{
  "points": [
    {"id": 1, "vector": [0.05, 0.61, 0.76, 0.74], "payload": {"city": "Berlin"}},
    {"id": 2, "vector": [0.19, 0.81, 0.75, 0.11], "payload": {"city": ["Berlin", "London"] }},
    {"id": 3, "vector": [0.36, 0.55, 0.47, 0.94], "payload": {"city": ["Berlin", "Moscow"] }},
    {"id": 4, "vector": [0.18, 0.01, 0.85, 0.80], "payload": {"city": ["London", "Moscow"] }},
    {"id": 5, "vector": [0.24, 0.18, 0.22, 0.44], "payload": {"count": [0]}},
    {"id": 6, "vector": [0.35, 0.08, 0.11, 0.44]}
  ]
}'
{"result":{"operation_id":0,"status":"completed"},"status":"ok","time":0.011843266}
```
WebUIから、Pointが作成されていることが確認できます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510470/f3283292-0a16-6745-1588-eb14889b6d3c.png)

curlでの確認結果は次の通りです。id `1` のPoint情報を取得しています。
```
$ curl -X GET "http://localhost:6333/collections/test_collection/points/1" -H "Content-Type: application/json"
{"result":{"id":1,"payload":{"city":"Berlin"},"vector":[0.05,0.61,0.76,0.74]},"status":"ok","time":0.0001196}
```

# 【おまけ】類似性検索
Pointを登録できたので、最後に類似性検索をしてみます。
Vectorとして[0.2,0.1,0.9,0.7]を与え、Top3を検索します。
```
$ curl -L -X POST 'http://localhost:6333/collections/test_collection/points/search' \
 -H 'Content-Type: application/json' \
  --data-raw '{
    "vector": [0.2,0.1,0.9,0.7],
    "with_vectors": true,
    "with_payload": true,
    "top": 3
}'
{"result":[{"id":4,"version":0,"score":1.362,"payload":{"city":["London","Moscow"]},"vector":[0.18,0.01,0.85,0.8],"order_value":null},{"id":1,"version":0,"score":1.273,"payload":{"city":"Berlin"},"vector":[0.05,0.61,0.76,0.74],"order_value":null},{"id":3,"version":0,"score":1.208,"payload":{"city":["Berlin","Moscow"]},"vector":[0.36,0.55,0.47,0.94],"order_value":null}],"status":"ok","time":0.0021894}
```
scoreが高い順に
id `4`
id `1`
id `3`
の3件がヒットすることを確認できました。

# 所感
手を動かすことで、ぼんやりとしていたQdrantに対する理解は、ある程度深まったと思います。

# 参考
https://qdrant.tech/documentation/overview/
https://qdrant.tech/documentation/concepts/storage/
https://qdrant.tech/documentation/concepts/payload/
https://qdrant.github.io/qdrant/redoc/index.html
https://qdrant.tech/documentation/concepts/search/#payload-and-vector-in-the-result