OllamaとQdrantでローカルベクター検索を構築する方法|Rustベース高性能ベクターDBをUbuntuに導入してRAGを高速化する手順

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)ローカルLLM > OllamaとQdrantでローカルベクター検索を構築する方法|Rustベース高性能ベクターDBをUbuntuに導入してRAGを高速化する手順
「pgvectorでRAGを組んだが、社内ドキュメントが数万件に増えてくると検索レスポンスが遅くなってきた」
「ベクター検索専用のDBに移行したいが、OllamaのEmbeddingとどう繋げればいいのかわからない」
そんな悩みを抱えるインフラエンジニアやMLOps担当者は多いはずです。この記事では、Rustで書かれた高性能ベクターデータベース「Qdrant」をUbuntu ServerにDockerで構築し、OllamaのEmbedding APIと連携してローカルRAGパイプラインを実装する手順を解説します。
pgvectorとの使い分け判断から、コレクション作成・ドキュメント登録・セマンティック検索・LLMによる回答生成まで、インターネットにデータを一切送らず社内完結で実現する方法を網羅します。

この記事のポイント

・nomic-embed-textのEmbeddingとQdrantを組み合わせてローカルRAGを構築できる
・docker runコマンド1つでQdrantサービスを起動し、Python qdrant-clientでコレクションを作成する
・pgvectorと異なり専用ベクターDBのためドキュメント数が増えてもミリ秒レベルの検索速度を維持できる
・本番運用時はQdrant Web UIとREST APIでコレクション状態を監視・管理する


OllamaとQdrantでローカルベクター検索を構築する方法|Rustベース高性能ベクターDBをUbuntuに導入してRAGを高速化する手順

「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

QdrantとpgvectorをRAGで使い分ける判断基準

RAG構築でよく比較されるのが、PostgreSQL拡張のpgvectorとQdrantです。どちらもOllamaのEmbedding APIと連携できますが、設計思想が根本的に異なります。
pgvectorはすでにPostgreSQLを使っている環境であれば追加インフラなしで導入でき、SQLで既存の業務データと結合できる点が強みです。一方、Qdrantはベクター検索専用に設計されているため、ANN(近似最近傍)インデックスの実装が最適化されており、ドキュメント数が数万件を超えるとレスポンス速度で明確な差が出てきます。
判断の目安は3点です。まず、社内ドキュメントが数千件程度でSQLによる複合条件検索が必要なら、pgvectorで十分です。ドキュメントが数万件以上になる、あるいは複数ユーザーが同時にクエリを投げる本番ワークフローであれば、Qdrantへの移行を検討する価値があります。さらに、PostgreSQLの運用負荷を切り離したい場合も、専用サービスとして独立したQdrantが管理しやすくなります。
社内でChatGPTが使えない環境にローカルLLMを導入する際の全体設計については、「社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢」も参考にしてください。

UbuntuにDockerでQdrantをインストールする手順

1. 事前確認

DockerがインストールされたUbuntu Server上での作業を前提とします。OllamaのセットアップとUbuntu Serverの基本設定については「Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド」を参照してください。
Dockerのバージョンを確認します。

$ docker --version Docker version 27.3.1, build ce12230

2. データ永続化ディレクトリを作成してQdrantコンテナを起動する

ストレージ用ディレクトリを作成し、QdrantのDockerイメージを起動します。ポート6333はREST APIとWeb UI、ポート6334はgRPC用です。

$ mkdir -p ~/qdrant/storage $ docker run -d \ --name qdrant \ --restart unless-stopped \ -p 6333:6333 \ -p 6334:6334 \ -v ~/qdrant/storage:/qdrant/storage \ qdrant/qdrant:latest

3. 起動確認

curlでヘルスチェックAPIを叩き、バージョン情報が返ってくれば正常です。

$ curl -s http://localhost:6333/ | python3 -m json.tool { "title": "qdrant - vector search engine", "version": "1.13.4", "commit": "..." }

`"title": "qdrant - vector search engine"` が含まれていれば起動完了です。
`--restart unless-stopped` を付けておくと、サーバー再起動後もDockerデーモンが自動的にQdrantコンテナを再開します。これはsystemdサービスとして常時起動させるOllamaと同様の運用思想です。

Ollamaでnomic-embed-textのEmbeddingモデルを準備する手順

1. Embeddingモデルを取得する

Ollamaのembedding用途として `nomic-embed-text` を使います。このモデルは768次元のベクターを生成し、英語・日本語を含む多言語テキストに対応しています。モデルサイズは約274MBとコンパクトで、CPUのみの環境でも実用的な速度で動作します。
どのEmbeddingモデルが自分の用途に合うかを先に整理したい場合は、「ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント」が参考になります。

$ ollama pull nomic-embed-text pulling manifest pulling 970aa74c0a90... 100% ▕████████████████████▏ 274 MB verifying sha256 digest writing manifest success

2. Embedding APIの動作を確認する

Ollama 0.4以降のEmbedding APIエンドポイントは `/api/embed`(単数形)です。テキストを渡して768次元の配列が返ってくれば正常です。

$ curl -s http://localhost:11434/api/embed \ -d '{"model":"nomic-embed-text","input":"Ollamaのテスト文章です"}' \ | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'次元数: {len(d[\"embeddings\"][0])}')" 次元数: 768

PythonでQdrantコレクションを作成してドキュメントを登録する手順

1. 必要なライブラリをインストールする

$ pip install qdrant-client requests

2. コレクションを作成するスクリプト

nomic-embed-textは768次元のため `size=768` を指定します。類似度計算方式は `Distance.COSINE`(コサイン類似度)を使います。テキスト検索では内積(DOT)より安定した結果が得られます。

# create_collection.py from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams client = QdrantClient(host="localhost", port=6333) client.create_collection( collection_name="local_docs", vectors_config=VectorParams(size=768, distance=Distance.COSINE), ) print("コレクション作成完了")

$ python3 create_collection.py コレクション作成完了

3. ドキュメントをベクター化して登録するスクリプト

OllamaのEmbedding APIで各ドキュメントを768次元のベクターに変換し、テキスト本文をpayloadとして一緒に格納します。payloadは検索結果から取り出すための任意のメタデータです。

# ingest_docs.py import requests from qdrant_client import QdrantClient from qdrant_client.models import PointStruct def get_embedding(text: str) -> list: resp = requests.post( "http://localhost:11434/api/embed", json={"model": "nomic-embed-text", "input": text}, ) return resp.json()["embeddings"][0] client = QdrantClient(host="localhost", port=6333) documents = [ {"id": 1, "text": "OllamaはローカルLLMの実行基盤であり、Linuxで手軽に動作する。"}, {"id": 2, "text": "systemdサービスとして登録すると、サーバー再起動後も自動起動する。"}, {"id": 3, "text": "nomic-embed-textは768次元のベクターを生成し、多言語テキストに対応する。"}, {"id": 4, "text": "Qdrantはコサイン類似度でベクターを比較し、関連度の高い文書を返す。"}, {"id": 5, "text": "RAGパイプラインは検索と生成を組み合わせてLLMの回答精度を高める手法だ。"}, ] points = [ PointStruct( id=doc["id"], vector=get_embedding(doc["text"]), payload={"text": doc["text"]}, ) for doc in documents ] client.upsert(collection_name="local_docs", points=points) print(f"{len(points)} 件のドキュメントを登録しました")

$ python3 ingest_docs.py 5 件のドキュメントを登録しました

セマンティック検索クエリをPythonから実行する手順

1. 検索スクリプトを作成する

クエリテキストをEmbedding APIでベクター化し、Qdrantの `search` メソッドで上位3件の類似ドキュメントを取得します。コサイン類似度のスコアは0.0~1.0の範囲で、0.8以上であれば高い関連度とみなせます。

# search_docs.py import requests from qdrant_client import QdrantClient def get_embedding(text: str) -> list: resp = requests.post( "http://localhost:11434/api/embed", json={"model": "nomic-embed-text", "input": text}, ) return resp.json()["embeddings"][0] client = QdrantClient(host="localhost", port=6333) query = "Ollamaを自動起動させる方法は?" results = client.search( collection_name="local_docs", query_vector=get_embedding(query), limit=3, ) for r in results: print(f"スコア: {r.score:.4f} | {r.payload['text']}")

2. 検索結果を確認する

$ python3 search_docs.py スコア: 0.8721 | systemdサービスとして登録すると、サーバー再起動後も自動起動する。 スコア: 0.7033 | OllamaはローカルLLMの実行基盤であり、Linuxで手軽に動作する。 スコア: 0.6248 | nomic-embed-textは768次元のベクターを生成し、多言語テキストに対応する。

クエリ「自動起動」に対して、systemdサービスに関するドキュメントが最高スコアで返ってきます。
SQLのLIKE検索とは異なり、「自動起動」というキーワードが含まれていなくても意味的に近い文書を発見できる点がセマンティック検索の強みです。「サーバー再起動後も動作させる方法は?」という言い換えクエリでも同じドキュメントが上位に来ます。

OllamaのLLMとQdrantを組み合わせたRAGパイプラインを実装する手順

1. 全体フローの設計

RAGパイプラインの流れは2ステップです。まずQdrantで質問に関連するドキュメントを検索し、次にその文書をコンテキストとしてOllamaのLLM(llama3.3:8b-instruct-q4_0など)に渡して回答を生成させます。
LLMはコンテキスト外の知識で答えるのではなく、提供された文書の範囲で回答するため、ハルシネーション(でたらめな回答)を抑制できます。社内ドキュメントや独自のナレッジベースを活用するRAGシステムの核心となる部分です。

2. RAGパイプラインスクリプトを作成する

# rag_pipeline.py import requests from qdrant_client import QdrantClient OLLAMA_BASE = "http://localhost:11434" EMBED_MODEL = "nomic-embed-text" LLM_MODEL = "llama3.3:8b-instruct-q4_0" def get_embedding(text: str) -> list: resp = requests.post( f"{OLLAMA_BASE}/api/embed", json={"model": EMBED_MODEL, "input": text}, ) return resp.json()["embeddings"][0] def query_llm(prompt: str) -> str: resp = requests.post( f"{OLLAMA_BASE}/api/generate", json={"model": LLM_MODEL, "prompt": prompt, "stream": False}, ) return resp.json()["response"] client = QdrantClient(host="localhost", port=6333) question = "Ollamaを自動起動させる方法は?" # Step1: 関連ドキュメントをQdrantで検索 results = client.search( collection_name="local_docs", query_vector=get_embedding(question), limit=3, ) context = "\n".join(f"- {r.payload['text']}" for r in results) # Step2: LLMにコンテキスト付きで回答を生成させる prompt = f"""以下の参考情報に基づいて質問に答えてください。参考情報にない内容は「情報がありません」と答えてください。 【参考情報】 {context} 【質問】 {question}""" print("=== 回答 ===") print(query_llm(prompt))

3. 実行結果を確認する

$ python3 rag_pipeline.py === 回答 === Ollamaをサーバー再起動後も自動起動させるには、systemdサービスとして登録する方法が有効です。 /etc/systemd/system/ollama.service にサービスユニットファイルを作成し、 systemctl enable ollama コマンドで自動起動を有効化してください。

プロンプトの末尾に「参考情報にない内容は情報がありませんと答えてください」という制約を入れることで、LLMがコンテキスト外の知識で勝手に回答することを防げます。本番では、コレクションに登録する文書の品質と範囲がRAG全体の精度を左右します。

Qdrant Web UIとREST APIでコレクションを管理・監視する手順

1. Web UIにアクセスする

QdrantはREST APIのポート6333でWeb管理UIも提供しています。ブラウザで以下のURLを開きます。

http://サーバーIPアドレス:6333/dashboard

コレクション一覧、登録ベクター数、インデックス状態がGUIで確認できます。リモートサーバーの場合はSSHポートフォワードを使ってローカルブラウザからアクセスする方法が安全です。

2. コレクション情報をAPIで確認する

$ curl -s http://localhost:6333/collections/local_docs | python3 -m json.tool { "result": { "status": "green", "vectors_count": 5, "indexed_vectors_count": 5, "points_count": 5, "config": { "params": { "vectors": { "size": 768, "distance": "Cosine" } } } } }

3. 監視で確認すべき項目

`status` が `green` であれば正常です。`yellow` はインデックス再構築中、`red` はエラー状態を示します。
`vectors_count` と `indexed_vectors_count` が一致していれば、すべてのベクターが検索インデックスに組み込まれています。大量ドキュメントを一括登録した直後は `indexed_vectors_count` が追いつくまで数秒かかることがあります。
コレクションを削除する場合は `DELETE http://localhost:6333/collections/コレクション名` を叩きます。**注意**: 削除は非可逆操作です。本番環境では必ずバックアップを取ってから実施してください。

トラブルシューティング|よくある問題と対処法

① Qdrantコンテナが起動しない(ポート競合)

ポート6333または6334が他のプロセスに使われていないか確認します。

$ ss -tlnp | grep -E '6333|6334'

別プロセスが使用中の場合は `docker run` の `-p` オプションを `-p 6380:6333 -p 6381:6334` のように変更し、Pythonコード側のポート番号も合わせて修正します。

② Embedding APIが `{"error":"..."}` を返す

Ollama 0.4以降では `/api/embed`(単数形)が正しいエンドポイントです。古いバージョンのコード例では `/api/embeddings`(複数形)が使われていることがありますが、バージョンによっては404になります。

$ ollama --version ollama version is 0.9.2

0.4未満の場合はアップデートを検討してください。`curl -fsSL https://ollama.com/install.sh | sh` で最新版に更新できます。

③ セマンティック検索のスコアが低く、期待する文書が上位に来ない

nomic-embed-textは英語中心の事前学習データで訓練されているため、日本語テキストで精度が下がる場合があります。日本語が多い社内ドキュメントには `mxbai-embed-large` への切り替えが有効です。

$ ollama pull mxbai-embed-large

mxbai-embed-largeは1024次元のため、Qdrantコレクションを `size=1024` で再作成してドキュメントを登録し直す必要があります。

④ コンテナ再起動後にデータが消える

バインドマウントのパスが正しいかを確認します。`docker inspect qdrant` で `Mounts` セクションを確認し、`~/qdrant/storage` が実際のホストパスにマウントされているか検証します。Dockerのパーミッション問題が発生した場合は `chmod 755 ~/qdrant/storage` で対処します。

まとめ

今回はOllamaのEmbedding APIとQdrantを組み合わせてローカルRAGパイプラインを構築する手順を解説しました。
操作 主なコマンド・スクリプト ポイント
Qdrant起動 docker run -d -p 6333:6333 -v ~/qdrant/storage:/qdrant/storage qdrant/qdrant:latest --restart unless-stoppedで自動再起動
Embeddingモデル取得 ollama pull nomic-embed-text 768次元・多言語対応・約274MB
コレクション作成 client.create_collection(size=768, distance=Distance.COSINE) モデルの次元数に合わせてsizeを設定
ドキュメント登録 client.upsert(collection_name="local_docs", points=[...]) payloadにテキスト本文を格納する
セマンティック検索 client.search(query_vector=get_embedding(query), limit=3) スコア0.8以上が高関連度の目安
RAGパイプライン 検索結果をLLMのpromptにcontextとして埋め込む コンテキスト外の回答を禁じる制約文を追加する
監視 curl http://localhost:6333/collections/local_docs status:greenを確認する
pgvectorは既存のPostgreSQL環境との親和性が高く、Qdrantはドキュメントが大規模になる本番ワークフローに向いています。どちらもOllamaのEmbedding APIと同じコードパターンで連携できるため、最初はpgvectorで試してドキュメント数が増えたらQdrantに移行するステップアップ構成も現実的な選択肢です。

ローカルRAGとOllamaを2日間のハンズオンで体験する

QdrantとOllamaの連携構築を手元の環境で試してみたい方、RAGパイプラインを実際に動かしながら理解したい方に最適なセミナーです。
実機GPU環境でEmbedding・ベクター検索・LLM回答生成を一気通貫で実装できます。実機GPU環境で手を動かしながら習得したい方向けに、「ローカルAIマスターセミナー」を開催しています。
少人数(最大8名)ZOOMハンズオン形式で実施しています。

>> ローカルAIマスターセミナーの詳細を確認する
ローカルLLMの構築・運用に関する関連記事もあわせて参考にしてください。

Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。