Home / AI & GenAI / Vector Databases

AI & GenAITroubleshootingIntermediate

Qdrant collection stuck in yellow or grey status: optimizers, indexing thresholds and fast bulk uploads

After a large upload the Qdrant collection shows yellow for hours, or grey after a restart, searches are slow and indexed_vectors_count stays far below points_count. Read the status correctly, trigger pending optimisations, and upload large datasets without making the optimizer do the work twice.

Deepak Bhardwaj5 min read

Applies toQdrant 1.9+ (grey status), self-hosted and Qdrant Cloud, REST and Python client

Symptoms

  • GET /collections/kb_chunks shows "status": "yellow" for a long time, or "grey".
  • indexed_vectors_count is much lower than points_count.
  • Searches are slow and CPU is high right after a bulk load.
  • Upload throughput drops as the collection grows.

What the statuses mean

Status Meaning
green All good; no optimisation running
yellow Optimisation running (indexing, merging or vacuuming segments). Searches work
grey Optimisations needed but not triggered, typically after a restart. Nothing will happen until an update arrives
red An optimisation failed; check logs

Searches work in all states except some red cases. They may be slower while segments are unindexed, because unindexed segments are searched by brute force.

Step 1: read the collection info

curl -s -H "api-key: $QDRANT_API_KEY" http://qdrant:6333/collections/kb_chunks | \
  jq '.result | {status, optimizer_status, points_count, indexed_vectors_count, segments_count, config: .config.optimizer_config}'
Field Look for
optimizer_status ok, or an error message
indexed_vectors_count vs points_count Gap = vectors in segments not yet HNSW-indexed
segments_count Very high numbers slow everything down
indexing_threshold Segment size (KB of vectors) above which a segment gets an HNSW index

indexed_vectors_count won't always equal points_count: small segments under indexing_threshold are intentionally left unindexed because brute force on them is fast. A large, persistent gap on a big collection is the problem.

Step 2: grey status - trigger the optimizer

Grey appears when Qdrant restarts with optimisations pending. Trigger them with an update to the collection; an empty optimizer config update is enough:

curl -X PATCH http://qdrant:6333/collections/kb_chunks \
  -H "api-key: $QDRANT_API_KEY" -H "Content-Type: application/json" \
  -d '{"optimizers_config": {}}'

Any point upsert also triggers it. Status should move to yellow, then green.

Step 3: yellow for hours - is it progressing?

# repeat every few minutes and compare
curl -s -H "api-key: $QDRANT_API_KEY" http://qdrant:6333/collections/kb_chunks | jq '.result.indexed_vectors_count'
Progress Cause Fix
Rising steadily Normal indexing of a large upload Wait; consider more CPU for the next load
Rising then resetting Continuous writes keep creating new segments to optimise Batch writes; tune below
Not moving Optimizer starved of CPU, or error Check logs and max_optimization_threads; look for disk full
kubectl logs -n qdrant qdrant-0 | grep -iE "optimi|error|panic|disk" | tail -30

Optimisation needs temporary disk space (it writes new segments before dropping old ones). A nearly full disk stalls it.

Step 4: bulk uploads the fast way

Uploading millions of points into a collection with HNSW enabled makes Qdrant build indexes on segments that are later merged and indexed again. For an initial load, defer the graph build:

from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://qdrant:6333", api_key=API_KEY, timeout=120)

# 1. disable HNSW graph building during the load
client.update_collection("kb_chunks", hnsw_config=models.HnswConfigDiff(m=0))

# 2. upload in parallel batches
client.upload_points(
    "kb_chunks",
    points=points_iter,           # generator of models.PointStruct
    batch_size=256,
    parallel=4,
    max_retries=3,
)

# 3. re-enable HNSW; the optimizer builds the graph once
client.update_collection("kb_chunks", hnsw_config=models.HnswConfigDiff(m=16))

Older guides set indexing_threshold: 0 to disable indexing during uploads; current Qdrant documentation recommends m: 0 for this. Use whichever your version's docs describe, and always switch it back.

Other bulk-load tips:

Tip Why
Create payload indexes before uploading Filter-aware graph links built in the same pass
Use gRPC (prefer_grpc=True) Lower serialisation overhead
wait=False on upserts during bulk load Don't block on each batch's application
Fewer, larger segments: default_segment_number matching CPU cores Less merging later
Vectors on_disk: true if RAM is tight Keeps upload from exhausting memory

Step 5: steady-state tuning

Setting Effect
optimizer_config.indexing_threshold Larger = fewer small indexes built, more brute-force on small segments
optimizer_config.max_optimization_threads CPU given to optimisation; limit it if search latency suffers during optimisation
optimizer_config.deleted_threshold Fraction of deleted points that triggers vacuum of a segment
optimizer_config.default_segment_number Target segment count; usually ≈ CPU cores

Change with PATCH /collections/{name} and optimizers_config.

Step 6: segment count too high

Thousands of segments usually come from very frequent small writes with wait=true, or settings that stop merging. Check max_segment_size and that the merge optimizer isn't throttled to zero threads. Let the optimizer catch up off-peak.

Decision table

Status / finding Action
Grey PATCH empty optimizers_config
Yellow, progressing Wait; plan bulk loads with m: 0
Yellow, stuck Logs, disk space, optimizer threads
Red Logs; fix cause (often disk); trigger again
Big index gap after load Bulk-load procedure next time

Prevent it next time

  • Script bulk loads: create collection + payload indexes → m: 0 → upload → m: 16 → wait for green.
  • Alert on collections not green for more than an hour, and on grey after restarts.
  • Keep 30%+ free disk for optimisation.
  • Record load time per million points to plan the next one.