OriginChainDB docs
examples · vector · 9 / 13

9. Train and install IVF centroids

← Vector examples

what this does

POST /v1/tenants/:t/vector/:table/train-and-install-centroids reads the vectors already stored in the table, runs mini-batch k-means over them, and installs the resulting centroid matrix as the table's IVF partitioning - in one call.

It then backfills a cell posting for every stored vector, so a table that was written before the install becomes queryable without a re-ingest. GET /v1/tenants/:t/vector/:table/centroids reads back what is installed.

when to use it

  • You want to query a table with "index": "ivf". Without installed centroids that query returns 503 - IVF has no partitioning to probe.
  • The corpus has grown or shifted since the last install and the cells no longer match the data.
  • You are moving a table off the default HNSW path onto IVF for a corpus large enough that a cell probe beats a graph walk.

the request

POST /v1/tenants/:t/vector/:table/train-and-install-centroids
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/vector/shop.products/train-and-install-centroids" \
  -H "Authorization: Bearer $OC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "partitions":            1024,
    "init":                  "kmeans_plus_plus",
    "max_iterations":        50,
    "batch_size":            1024,
    "convergence_threshold": 1e-5,
    "seed":                  42
  }'

what you get back

{
  "trained":              true,
  "installed":            true,
  "partitions":           1024,
  "dim":                  768,
  "iterations":           37,
  "converged":            true,
  "last_max_shift":       0.0000073,
  "training_corpus_size": 100000,
  "populated":            100000
}

// populate_skipped is present only when the backfill did not run.

populated is the number of stored vectors that got a cell posting from this call. When it is well below training_corpus_size, or a populate_skipped string is present, the index is installed but not fully covering the corpus - the coverage report will show it.

request fields

FieldRequiredNotes
partitionsyesHow many Voronoi cells to train. The engine caps this at 65,536, and refuses below the k-means floor described under common mistakes.
initno"kmeans_plus_plus" or "random_sample". Defaults to "random_sample".
max_iterationsnoMini-batch iteration cap. Defaults to 100.
batch_sizenoMini-batch size. Defaults to 1024.
convergence_thresholdnoEarly-stop threshold on the per-iteration maximum centroid shift. Defaults to 1e-4.
seednoPRNG seed. Pass one if you want two runs over the same corpus to produce the same partitioning.

reading back what is installed

GET /v1/tenants/:t/vector/:table/centroids
curl "https://$OC_HOST/v1/tenants/$OC_TENANT/vector/shop.products/centroids" \
  -H "Authorization: Bearer $OC_TOKEN"

what the read-back returns

{
  "installed":  true,
  "partitions": 1024,
  "dim":        768,
  "centroids_preview": [
    [0.0124, -0.0883, 0.0451, 0.0037, -0.0192, 0.0608, -0.0271, 0.0115]
    /* ... first 4 centroids, first 8 dims of each ... */
  ]
}

The preview is deliberately truncated to the first 4 centroids and the first 8 dimensions of each, so the response stays small whatever partitions × dim is. It is an "is anything installed" check, not a way to export the matrix. A table with nothing installed answers 200 with "installed": false and zeroes - not 404.

how it works

The trainer reads up to one million stored vectors, runs mini-batch k-means to max_iterations or until the maximum centroid shift falls under convergence_threshold, and writes the matrix in a single batch. Steps before that write touch nothing, so a failure leaves the previous partitioning in place.

Installing is a full replace, and a replace re-draws the cell boundaries. That is why the install then purges the old cell postings and streams the stored corpus back through the new assignment, one page at a time - reading under a short shared lock, assigning with no lock held, appending under a short exclusive one. The walk is not capped: every stored vector gets a posting.

If you already have a centroid matrix from your own training run, POST /v1/tenants/:t/vector/:table/install-centroids takes it directly as {"centroids": [[...], [...]]} and derives partitions and dim from the shape of what you send.

common mistakes

  • Training on too few vectors. The engine refuses with 400 when the stored count is under partitions × 4. Past that floor k-means puts several centroids on the same point. Lower partitions or ingest more before training.
  • Querying IVF before installing. A "index": "ivf" query against a table with no centroids returns 503 with "error": "ivf_centroids_not_installed" and an install_url pointing here.
  • Assuming it is quick. Training and the backfill both run synchronously inside the request. On a large corpus the call holds the connection for seconds - set a generous client timeout.
  • Re-installing and expecting the old postings to survive. They do not, and should not: new centroids mean new cells. The purge and re-populate is the point, and re-running the route converges to the same end state.
  • Reading centroids_preview as the matrix. It is four centroids, eight dimensions each, always.