Qdrant gRPC Endpoint
NornicDB can expose a Qdrant-compatible gRPC API so you can use existing Qdrant SDKs (multi-language) against NornicDB without rewriting your client code.
This endpoint is intended for:
- high-throughput vector ingest (upsert/delete/update vectors)
- low-latency vector search (SearchPoints / Query / Scroll / Count)
- “Qdrant client compatibility” integrations (migration / parity testing / drop-in usage)
NornicDB does not implement the Qdrant REST API. This feature is gRPC only (default port
6334).
Enable the endpoint
Environment variables
export NORNICDB_QDRANT_GRPC_ENABLED=true
export NORNICDB_QDRANT_GRPC_LISTEN_ADDR=":6334" # optional (default :6334)
export NORNICDB_QDRANT_GRPC_TLS_ENABLED=true
export NORNICDB_QDRANT_GRPC_TLS_CERT=/tls/grpc.crt
export NORNICDB_QDRANT_GRPC_TLS_KEY=/tls/grpc.keyYAML config
features:
qdrant_grpc_enabled: true
qdrant_grpc_listen_addr: ":6334"
qdrant_grpc_max_vector_dim: 4096
qdrant_grpc_max_batch_points: 1000
qdrant_grpc_max_top_k: 1000
qdrant_grpc_tls_enabled: true
qdrant_grpc_tls_cert: "/tls/grpc.crt"
qdrant_grpc_tls_key: "/tls/grpc.key"Docker / Compose
Expose the gRPC port:
ports:
- "7474:7474" # HTTP
- "7687:7687" # Bolt
- "6334:6334" # Qdrant gRPC
environment:
- NORNICDB_QDRANT_GRPC_ENABLED=${NORNICDB_QDRANT_GRPC_ENABLED:-false}
- NORNICDB_QDRANT_GRPC_LISTEN_ADDR=${NORNICDB_QDRANT_GRPC_LISTEN_ADDR:-0.0.0.0:6334}
- NORNICDB_QDRANT_GRPC_TLS_ENABLED=${NORNICDB_QDRANT_GRPC_TLS_ENABLED:-false}
- NORNICDB_QDRANT_GRPC_TLS_CERT=${NORNICDB_QDRANT_GRPC_TLS_CERT:-}
- NORNICDB_QDRANT_GRPC_TLS_KEY=${NORNICDB_QDRANT_GRPC_TLS_KEY:-}
- NORNICDB_QDRANT_GRPC_TLS_CLIENT_CA=${NORNICDB_QDRANT_GRPC_TLS_CLIENT_CA:-}
- NORNICDB_QDRANT_GRPC_TLS_CLIENT_AUTH_MODE=${NORNICDB_QDRANT_GRPC_TLS_CLIENT_AUTH_MODE:-none}The shipped Compose files also forward the vector-dimension, batch-size, and top-k limits and TLS/mTLS settings. Mount certificate files into the container when using native TLS. For secure deployment topologies, see Qdrant gRPC reverse proxying.
Request logging
Unary and streaming request completion events use NornicDB's configured logger and NORNICDB_LOG_LEVEL. Successful requests log at debug, so the default info level does not write a log for every successful search. Client/protocol failures log at warning; Unknown, Internal, DataLoss, and Unavailable failures log at error. Events include the RPC method/type, status code, outcome and duration; request payloads and authentication metadata are not included.
To inspect successful requests, set NORNICDB_LOG_LEVEL=DEBUG. Applications that embed qdrantgrpc.NewServer directly can supply Config.Logger; nil uses an info-level text logger on stderr. The production server injects its configured structured logger, including its output, redaction and level policy.
TLS, mTLS, and application authentication
Authenticated public listeners require native TLS. Clients must trust the CA that issued NORNICDB_QDRANT_GRPC_TLS_CERT and connect using TLS on port 6334. To require client certificates as well, set:
export NORNICDB_QDRANT_GRPC_TLS_CLIENT_CA=/tls/client-ca.crt
export NORNICDB_QDRANT_GRPC_TLS_CLIENT_AUTH_MODE=require_verifySupported client-auth modes are none, request, request_verify, and require_verify. The verifying modes require a client CA. With NornicDB authentication enabled, transport authentication is additive: clients must still send authorization: Basic ..., authorization: Bearer ..., or x-api-key metadata. NornicDB then applies the configured gRPC RBAC policy.
Plaintext gRPC remains supported on loopback for a same-host TLS proxy. It is rejected on a public interface when application authentication is enabled.
Embedding ownership (critical)
NornicDB can run in two modes:
- NornicDB-managed embeddings (
NORNICDB_EMBEDDING_ENABLED=true)
- NornicDB owns embedding generation and storage.
- Qdrant “vector mutation” RPCs (e.g. Upsert/UpdateVectors/DeleteVectors) may be rejected with
FailedPreconditionas a policy choice (to avoid multiple external writers). - Qdrant text queries via
Points.Queryare supported (NornicDB embeds the query).
- Client-managed vectors (
NORNICDB_EMBEDDING_ENABLED=false)
- Your Qdrant client owns vectors and can upsert/update/delete vectors normally.
- Best fit when you want to use Qdrant SDKs “as-is”.
Note: Qdrant vectors are stored in Node.NamedEmbeddings, while NornicDB-managed embeddings are stored in Node.ChunkEmbeddings, so the two embedding systems do not overwrite each other. For the internal details, see docs/architecture/embedding-search.md.
If you want to use Qdrant SDKs for ingestion, set:
export NORNICDB_EMBEDDING_ENABLED=falseCollection & point mapping
At a high level:
- Qdrant collection → a NornicDB database (namespace).
- The Qdrant gRPC layer uses
DatabaseManager.GetStorage(collectionName); all point data lives under the namespace prefixcollectionName:. - A collection is considered “Qdrant-managed” if it contains
_collection_meta(created byCreateCollection).
- The Qdrant gRPC layer uses
- Qdrant point → a NornicDB node inside the collection/database namespace:
- Node ID:
qdrant:point:<raw-id>(does not include the collection name) - Labels:
QdrantPoint,Point - Payload: mapped to
node.Properties(with internal_qdrant_*keys removed from responses) - Vector(s): stored in
Node.NamedEmbeddingsand indexed for vector search
- Node ID:
NornicDB also supports named vectors in Qdrant:
- Qdrant named vectors →
node.NamedEmbeddings[vectorName] - This enables multiple independent embedding fields per point (e.g.
titleandcontent).
Vector search uses the database's index
A Qdrant Search runs on the collection database's search index, the same index and query as Cypher's db.index.vector.queryNodes on that database:
- ANN strategy: the configured strategy applies,
NORNICDB_VECTOR_ANN_QUALITYincluded (HNSW presets,compressedIVF-PQ, k-means routing, brute force), with the same exact rescoring. hnsw_ef:params.hnsw_efsets the HNSW beam width for that request on HNSW-based strategies. Without it, a search uses the configured preset'sefSearch(50 forfast, 100 forbalanced, 200 foraccurate, orNORNICDB_VECTOR_HNSW_EF_SEARCH), as Qdrant does. IVF-PQ, k-means routing and brute force don't search a graph and ignore it.- Exact queries:
params.exact=trueevaluates the complete indexed population;QueryandQueryBatchpreserveparams, asSearchandSearchBatchdo. - Readiness: lazy indexes warm before the first answer. A failed or incomplete build returns an error rather than a successful partial result.
- Collection configuration:
CreateCollection.hnsw_configandquantization_configare accepted but do not configure per-collection indexes. Configure ANN through NornicDB settings;GetCollectionreports the configured HNSW preset's M and construction beam.quantization.rescoredoes not disable NornicDB's exact rescoring. - Payload filters: filters are applied while the search widens, so a selective filter still returns its top matches.
- Scores: Dot and Euclid use original stored magnitudes, including with file-backed indexes. Qdrant's raw similarities: cosine and dot as-is, Euclid as
1 / (1 + distance). - Restart: points stay searchable across a restart, since the index is rebuilt from the stored vectors.
- Vector search disabled: a database with vector search disabled returns no hits, as it does for Cypher.
Cypher access (collection = database)
Because collections are databases, you can query points with Cypher:
USE my_vectors
MATCH (p:QdrantPoint)
RETURN count(p)Dropping a collection is the same as dropping a database:
DROP DATABASE `my_vectors`Quick check: is the gRPC endpoint reachable?
If you have grpcurl installed:
grpcurl -plaintext 127.0.0.1:6334 grpc.health.v1.Health/CheckClient examples (multi-language)
Python (official qdrant-client, gRPC)
pip install qdrant-clientfrom qdrant_client import QdrantClient
from qdrant_client.http import models as m
client = QdrantClient(host="127.0.0.1", grpc_port=6334, prefer_grpc=True)
client.create_collection(
collection_name="my_vectors",
vectors_config=m.VectorParams(size=128, distance=m.Distance.COSINE),
)
client.upsert(
collection_name="my_vectors",
points=[
m.PointStruct(id="p1", vector=[1.0] * 128, payload={"tag": "first"}),
m.PointStruct(id="p2", vector=[0.5] * 128, payload={"tag": "second"}),
],
)
results = client.search(
collection_name="my_vectors",
query_vector=[1.0] * 128,
limit=10,
with_payload=True,
)
print([r.id for r in results])Named vectors:
client.create_collection(
collection_name="docs",
vectors_config={
"title": m.VectorParams(size=128, distance=m.Distance.COSINE),
"content": m.VectorParams(size=128, distance=m.Distance.COSINE),
},
)
client.upsert(
collection_name="docs",
points=[
m.PointStruct(
id="doc1",
vector={"title": [1.0]*128, "content": [0.2]*128},
payload={"kind": "article"},
)
],
)
results = client.search(
collection_name="docs",
query_vector=("title", [1.0]*128),
limit=5,
)Go (direct gRPC using upstream Qdrant protos)
package main
import (
"context"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
qdrant "github.com/qdrant/go-client/qdrant"
)
func main() {
ctx := context.Background()
conn, err := grpc.Dial("127.0.0.1:6334", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
defer conn.Close()
points := qdrant.NewPointsClient(conn)
resp, err := points.Search(ctx, &qdrant.SearchPoints{
CollectionName: "my_vectors",
Vector: make([]float32, 128),
Limit: 10,
})
if err != nil {
log.Fatal(err)
}
_ = resp
}Rust (gRPC, conceptually)
Use the official Qdrant client that supports gRPC (or generated gRPC stubs) and point it at 127.0.0.1:6334.
If you prefer a “known good” reference, see:
scripts/qdrantgrpc_e2e_python.py(Python SDK flow)scripts/qdrantgrpc_e2e_client.go(Go gRPC flow)
Text queries (Qdrant Points.Query with Document.text)
If NornicDB-managed embeddings are enabled (NORNICDB_EMBEDDING_ENABLED=true), you can send a text query using the upstream Qdrant protobuf contract:
Points/QuerywithQuery.nearest(VectorInput.document(Document{text: ...}))
This is useful when you want “Qdrant-shaped” inference queries without custom APIs.
Compatibility + supported RPCs
See:
pkg/qdrantgrpc/README.mdfor implementation details and examplespkg/qdrantgrpc/COMPAT.mdfor the supported RPC matrix
Troubleshooting
gRPC not listening
- Confirm
NORNICDB_QDRANT_GRPC_ENABLED=true - Confirm port
6334is exposed (Docker) and not already in use.
FailedPrecondition on Upsert / UpdateVectors / DeleteVectors
This typically means NornicDB-managed embeddings are enabled. If you want the client to own vectors:
export NORNICDB_EMBEDDING_ENABLED=false